一句话:认证内置在每个 LingCode Cloud 后端里——无需另起一套服务。用户可以用邮箱/密码、魔法链接、邮箱 OTP 验证码,或 Google/GitHub/Apple OAuth 登录;你还可以强制要求 TOTP 多因素认证。登录之后,客户端 SDK 会保存会话,并把它附加到此后每一次 .from() 调用上,于是行级安全(RLS)就能以当前登录用户的 id 作为依据。anon key 是公开的;真正用来认证某个人身份的是用户会话令牌。
如果你用过 Firebase Auth 或 Supabase Auth,这套形态会很熟悉:几种登录方式、一个由 SDK 替你携带的会话令牌,以及服务端规则来决定每个用户能看到什么。本页先讲清楚模型——为什么 anon key 可以安全地随包发布、会话令牌到底是什么、多因素如何抬高门槛——然后用可运行的代码逐一走过每种方式。示例里的 lingcode 客户端就是 SDK 客户端;在 /try 和 Mac 预览里它已被预先注入为 window.lingcode。
每个后端都暴露两类凭证,把它们分清楚,就理解了这里认证的大半。
auth.uid() 这类规则开始匹配,每个用户只看到属于自己的行。把一个密钥放进谁都能读的客户端 JavaScript 里,感觉很别扭。它没问题的原因是:anon key 是一个地址,不是一个权限。它告诉网关你指的是哪个后端,并让未认证的读取通往你策略标记为 public 的内容——仅此而已。真正为私有数据把关的,是用户会话令牌加上你在服务端的行级安全策略。把 anon key 当作一个公开 URL,把你真正的密钥(Stripe 密钥、厂商 API key)当作密码——后者放进密钥保险库,绝不放进客户端。
一次成功的登录会解析为一个 Session。SDK 替你持久化它,所以你很少需要手动读取令牌:
Session = {
user: { id, email } | null,
token: string // 一个代表该用户的已签名 JWT
}
每个认证调用都返回一个 Result——{ data, error }——除了下面注明的少数几个。在信任 data 之前先检查 error。
await client.ready 一次。如果用户是从 OAuth 跳转或魔法链接返回的,会话会随 URL 一起到达,SDK 必须先消费掉它,getUser() 才能告诉你谁登录了。await lingcode.ready 在这件事做完之后才会 resolve——读取用户之前先调用它一次,否则刚跳转回来的用户会有一帧看起来像是未登录。
邮箱/密码、魔法链接和邮箱 OTP 在每个后端上始终可用。三个托管 OAuth 提供方——Google、GitHub、Apple——由运营方配置并共享,或者你也可以为每个后端自带 OAuth 凭证。在控制台里开启并管理 OAuth 提供方,以及「强制要求 MFA」这个设置。
经典凭证。signUp 创建账号,signIn 返回一个会话。始终可用。
邮件发送一个一键登录链接。无需记住密码;用户返回时 SDK 会完成这个链接。
邮件发送一段短数字验证码,让用户输入回来。适合移动端和免密流程。
Google、GitHub、Apple(托管或自带),或一个自定义提供方字符串。顶层跳转。
在以上任意方式之上叠加身份验证器 app 的验证码。把会话提升到 AAL2。
最简单的路径。signUp 注册账号,signIn(别名 signInWithPassword)返回一个会话。两者都接收 { email, password } 并返回 Result<Session>。
// 注册
const { data, error } = await lingcode.auth.signUp({
email: '[email protected]',
password: 'a-strong-passphrase'
});
// 登录(signInWithPassword 是别名)
const { data: session, error: err } = await lingcode.auth.signIn({
email: '[email protected]',
password: 'a-strong-passphrase'
});
if (!err) {
console.log('signed in as', session.user.email);
}
调用成功后,SDK 已经保存好了会话——你下一次 lingcode.from(...) 调用就会带着认证发出去。你不需要自己把令牌传到任何地方。
魔法链接用一个发给用户的一次性链接替换掉密码。你发送链接;用户点击后会带着 URL 里的会话回到你的应用,SDK 会自动完成它——这就是为什么你很少直接调用 verifyMagicLink。
// 1. 发送链接
const { data, error } = await lingcode.auth.sendMagicLink({
email: '[email protected]',
redirectTo: 'https://your-app.example.com/welcome' // 可选
});
// data => { sent: true }
// 2. 在链接返回到的那个页面上,只需 await ready ——
// SDK 会替你从 URL 里消费掉令牌。
await lingcode.ready;
const user = lingcode.auth.getUser(); // 现在已填充
// (很少需要)手动完成一个令牌:
// await lingcode.auth.verifyMagicLink(tokenFromUrl);
OTP 和魔法链接是同一个思路,只不过用户输入一段短验证码,而不是点击一个链接——在移动端、或深链不方便时很顺手。先发送一个验证码,然后校验用户输入的验证码。
// 1. 发送一个验证码
await lingcode.auth.sendOtp({ email: '[email protected]' });
// => { sent: true }
// 2. 校验用户输入的内容
const { data: session, error } = await lingcode.auth.verifyOtp({
email: '[email protected]',
code: '482913'
});
if (!error) {
// 会话已保存;用户已登录
}
OAuth 把登录交给一个用户已经信任的提供方。signInWithOAuth 接收一个提供方——'google'、'github'、'apple',或一个自定义字符串——并执行一次到该提供方授权页的顶层导航。它返回 void,而不是 Result,因为页面正在离开;返回时,SDK 会自动保存会话。
// 触发一次到 Google 的整页跳转。
lingcode.auth.signInWithOAuth('google', {
redirectTo: 'https://your-app.example.com/dashboard' // 可选
});
因为这是一次顶层导航,不要指望这一行之后的代码能拿到结果运行——浏览器已经在前往提供方的路上了。在用户返回到的那个页面上处理登录状态(await ready,然后读取用户)。
不是每个后端都开启每个提供方。在渲染按钮之前先问一下——getProviders() 返回一个 提供方 → 可用性 的映射,于是你可以只展示能用的那些:
const providers = await lingcode.auth.getProviders();
// {
// google: { available: true, source: 'managed' },
// github: { available: true, source: 'byo' },
// apple: { available: false, source: null }
// }
if (providers.google?.available) {
renderGoogleButton();
}
如果提供方往返失败了(用户取消、客户端配置有误等),错误码会随 URL 一起带回来。lastError() 把它暴露出来,让你能在跳转之后展示一条消息:
await lingcode.ready;
const err = lingcode.auth.lastError(); // 例如 'access_denied' 或 null
if (err) showBanner(`Sign-in failed: ${err}`);
接入 Google、GitHub 或 Apple,你有两种方式。托管提供方由运营方配置并在各后端间共享——最快的路径,你无需注册任何东西。自带让你在控制台里为每个后端提供自己的 client id 和 secret,于是授权页显示的是你应用的名字,而且你拥有这个 OAuth 应用。在 lingcode.dev/backends → 你的后端 → 认证提供方 里切换两者,并翻转「强制要求 MFA」设置。分步操作:用 Google 登录 和 用 GitHub 登录。
密码或 OAuth 登录证明了用户知道某个秘密、或掌控着某个账号——保证级别 AAL1。MFA 加上第二重证明:来自身份验证器 app 的基于时间的一次性验证码(TOTP——RFC 6238,6 位数字,每 30 秒刷新一次,带 ±1 步的漂移容忍,所以时钟稍有偏差也能验证通过)。完成 MFA 会把会话提升到 AAL2——你的敏感操作可以要求的那个更高保证级别。
这个流程有两部分。注册(Enroll)一次:后端返回一个 secret 加一个二维码,用户把它扫进身份验证器 app,你再校验一个验证码以确认配对。此后是校验(Verify):当某个会话需要 AAL2 时,用户输入一个当前验证码,会话即被提升。如果后端开启了「强制要求 MFA」,用户必须先完成 MFA 才被视为完全登录。
把保证拆成 AAL1 和 AAL2,让你可以划一条线:日常读取发生在 AAL1,但一条策略可以为危险操作要求 AAL2——删除账号、转移资金、修改安全设置。一个被盗的密码只能让攻击者到达 AAL1;没有用户的身份验证器设备,他们无法到达 AAL2,于是那些高价值路径保持关闭。在后端强制要求 MFA,不过是把 AAL2 设为「登录」本身的门槛。
MFA 通过下面的 REST 端点驱动——先 enroll 拿到 secret 和二维码,再 verify 一个验证码以到达 AAL2:
// Enroll:返回 { secret, qr_code } —— 渲染二维码让用户扫描
POST /api/cloud/be/<backend-id>/auth/mfa/enroll
// Verify 一个验证码以确认注册 / 把会话提升到 AAL2
POST /api/cloud/be/<backend-id>/auth/mfa/verify { "code": "482913" }
完整演练,包括打开「强制要求 MFA」开关:在你的后端强制要求 MFA。
会话令牌是一个代表用户的已签名 JWT。SDK 替你携带它,但了解它在传递什么会有帮助:
/auth/token/refresh。?apikey=)用于匿名 / RLS-public 访问,要么携带用户令牌(由 SDK 自动附加)用于已登录访问。同样的端点,不同的凭证。需要时手动读取当前用户或令牌:
const user = lingcode.auth.getUser(); // { id, email } | null
const token = lingcode.auth.getToken(); // string | null(即 JWT)
await lingcode.auth.signOut(); // 清除已保存的会话;resolve 为 { error: null }
SDK 是这些端点之上的一层薄封装——如果你在 JavaScript 之外工作,或想确切看到正在发生什么,可以直接使用它们。一个后端的基址是 https://lingcode.dev/api/cloud/be/<backend-id>。登录和提供方端点用 anon key 认证(Bearer 或 ?apikey=);MFA、刷新和登出端点需要用户令牌。
| Method | Path | Body / 说明 |
|---|---|---|
| POST | /auth/signup | { email, password } |
| POST | /auth/signin | { email, password } |
| POST | /auth/magiclink/request | { email, redirect_url } |
| POST | /auth/magiclink/verify | { token } |
| POST | /auth/otp/request | { email } |
| POST | /auth/otp/verify | { email, code } |
| POST | /auth/apple/native | { identity_token } —— 原生 Apple 登录 |
| POST | /auth/token/refresh | { refresh_token } —— 需要用户令牌 |
| POST | /auth/signout | { refresh_token } 或 { all: true } —— 需要用户令牌 |
| POST | /auth/mfa/enroll | → { secret, qr_code } —— 需要用户令牌 |
| POST | /auth/mfa/verify | { code } → AAL2 级别的会话 —— 需要用户令牌 |
| POST | /auth/mfa/challenge | 需要用户令牌 |
| GET | /auth/mfa/factors | 需要用户令牌 |
| DELETE | /auth/mfa/factors/:factorId | 需要用户令牌 |
| GET | /auth/providers | → { google:{available,source}, github:{…}, apple:{…} } |
| GET | /auth/oauth/:provider/start?redirect_url=… | → 302 到该提供方的授权页 |
OAuth 往返在一个由平台拥有的回调处收尾:
GET/POST /api/cloud/auth/oauth/:provider/callback
// 成功时,带着以下参数跳转回你的 redirect_url:
// ?lc_session=<jwt>
// 失败时:
// ?lc_error=<code>
?apikey=)。MFA 端点、令牌刷新和登出需要用户令牌——你无法仅凭 anon key 注册一个因子或刷新一个会话。SDK 会替你挑对的那个;如果你在手动调用 REST,请发送匹配的凭证。