跳转到内容

完成第一次登录

查看 Markdown

本教程会带一个 Web 应用从门户配置一直走到第一次完整登录。完成后,你的后端将持有一位已通过 Sudomimus 认证的用户所对应的 access token 与 refresh token。

不是 Web 应用?请选对应指南:

  1. 在 with.sudomimus.com 创建或加入一个组织(Organization)。 开发者自助门户以组织为单位:应用(Application)与扇区(Sector)都归属于某个组织,因此在创建应用之前你需要先拥有一个。大多数账户会当场创建自己的第一个组织(表单会预填一个建议名称);如果同事已经在运行一个,请让他们邀请你加入。在你尚未归属任何组织之前,/applications 与 /sectors 页面会重定向到 /organizations。

  2. 在该组织内创建你的应用。 创建时你会拿到:

    • applicationAnchor —— 一个稳定的小写 kebab-case 应用锚点(例如 my-app),也是应用在 API 中的公开名称。
    • client-auth 私钥 —— 创建时只展示一次,用于签名 /establish 请求。请像对待任何生产密钥一样妥善保管。
    • 每个应用都有一个 Session JWKS URL:https://session-api.sudomimus.com/applications/{applicationAnchor}/jwks.json,按 kid 验证 access / refresh token。密钥轮换在应用详情的“签名密钥”标签页管理;创建响应不会下发一份一次性的签名 PEM。
  3. 至少添加一条 CALLBACK 类型的 返回规则,列出允许重定向回的 hostname。具体的 callbackUrl 在每次 /establish 时由应用提供;规则决定的是这个 URL 的 hostname 是否被允许。

  4. 至少添加一条认证规则(例如 PASSKEY_USERNAMELESS、PASSKEY_REASONED 或 EMAIL_VERIFICATION)和一条身份准入规则(例如用于开放注册的 EMAIL + allowedEmails: ["*"])。三层规则都采用允许列表 + 默认拒绝;任一层为空时,应用都无法完成登录。

  5. 激活应用。 新应用初始状态为 DRAFT。上线准备、激活、停用与重新启用的权威说明见应用上线。

每一次经过 Connect API 的身份认证往返都有三个阶段,随后进入共享的 Session API refresh 阶段:

  1. Establish(建立) —— 应用后端请求 Connect 开启一次认证会话,并取回一对会话引用(exposureKey + hiddenKey)。
  2. Authenticate(认证) —— 应用携带 exposureKey 把用户引导到 via.sudomimus.com;用户在那里完成通行密钥或邮箱验证码挑战。
  3. Redeem(兑换) —— via.sudomimus.com 通过回调 URL 交还控制权,并在查询参数中附带 exposure-key 和 confirmation-key。应用后端将三个密钥提交给 Connect,换取签名的访问令牌和刷新令牌。
  4. Refresh(刷新) —— 访问令牌临近过期时,应用后端调用 Session API,使用刷新令牌换取新的访问令牌和轮换后的刷新令牌。

完整请求形状与 Connect、via.sudomimus.com、应用之间的协作关系见 Connect 流程。

下面是与框架无关的后端骨架。请把 pendingSessions 和响应辅助函数替换为你的框架所提供的服务端 session store 与 HTTP 原语。绝不能把 client-auth 私钥或 hiddenKey 放进浏览器代码。

终端窗口
pnpm add @sudomimus/connect

以下值只能放在后端环境中:

SUDOMIMUS_APPLICATION_ANCHOR=your-application
SUDOMIMUS_CLIENT_AUTH_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
SUDOMIMUS_CALLBACK_URL=https://your-app.com/auth/callback

callback hostname 必须符合你在门户中配置的 CALLBACK Return Rule。

import { ConnectClient, RETURN_METHOD } from "@sudomimus/connect";
const applicationAnchor = process.env.SUDOMIMUS_APPLICATION_ANCHOR!;
const client = new ConnectClient({
clientAuth: {
applicationAnchor,
privateKeyPem: process.env.SUDOMIMUS_CLIENT_AUTH_PRIVATE_KEY!,
},
});
const inquiry = await client.establish({
applicationAnchor,
returnMethods: [{
type: RETURN_METHOD.CALLBACK,
payload: { callbackUrl: process.env.SUDOMIMUS_CALLBACK_URL! },
}],
});
await pendingSessions.save(inquiry.exposureKey, inquiry.hiddenKey);
const hostedLogin = new URL("https://via.sudomimus.com/");
hostedLogin.searchParams.set("exposure-key", inquiry.exposureKey);
return redirect(hostedLogin.toString(), 302);

在浏览器中打开这个路由。Sudomimus 会展示 Layer 1 规则所允许的认证方式之一;使用测试账户完成挑战。

认证完成后,Sudomimus 会把浏览器重定向到 callback,并附上 exposure-key 与 confirmation-key 查询参数:

const callbackUrl = new URL(request.url);
const exposureKey = callbackUrl.searchParams.get("exposure-key");
const confirmationKey = callbackUrl.searchParams.get("confirmation-key");
if (exposureKey === null || confirmationKey === null) {
throw new Error("Missing Sudomimus callback keys");
}
const hiddenKey = await pendingSessions.take(exposureKey);
const tokens = await client.redeem({
exposureKey,
hiddenKey,
confirmationKey,
});

take 应原子地消费服务端暂存值,使 callback 无法复用它。兑换成功后会返回 accessToken 与 refreshToken。

使用应用的 Session JWKS 验证 tokens.accessToken,检查签名、kid、typ、issuer、audience、过期时间和 actor shape;验证成功后,从 sub 读取应用可见的用户标识符。请遵循完整的令牌验证流程,不要把仅仅 decode 当作验证。

把 refresh token 存入受保护的服务端存储,再按照管理会话完成轮换。callback 只能成功兑换一次、token 验证通过、应用建立自己的本地 session,并且日志和 URL 都没有泄露 token 时,第一次登录路径才算完成。