外观
授权界面 apps/auth
NATLAN DN42 身份服务的 OAuth / OIDC 授权界面,生产入口 https://auth.natlan.io/authorize。
四个站里的两个例外:不用 SvelteKit,不部署成 Cloudflare Worker。两条例外都 有具体理由,见第 5 节与 internals/auth-hosting.md。
1. 它做什么
它是 OAuth 授权码流里那一屏「请确认授权」,前面挂着一整套 DN42 身份验证:
/authorize?client_id=…&redirect_uri=…&…
↓ 后端校验并归一化请求参数(前端从不信任原始 query)
① ASN 输入 ASN,查 registry 得到 mntner 与可用验证方式
② 验证方式 email / PGP / SSH —— 由服务端下发,含「这一项是否需要人机验证」
③ 应答 按方式完成挑战(收码 / 签名)
④ 授权 展示 client 与 scope,同意或拒绝 → 后端返回 redirect_topasskey 快路径贯穿全程:上次验证成功的 ASN 记在 dn42.auth.lastAsn,下次进入 页面时会无提示地发起一次 passkey 登录尝试;任何失败(未注册、用户关闭对话框、 Safari 拒绝无用户激活的凭据请求)都是静默的——常规的 ASN 步骤已经在下面就位并预填好, 方式步骤里也仍然提供手动 passkey 按钮。验证通过后可在同意页注册 / 管理凭据。
2. 契约要点
| 要点 | 说明 |
|---|---|
| 重定向目标只来自后端响应 | 绝不从原始 query 里取。ContextRedirect 与 approve 的 redirect_to 是唯一来源 |
| 人机验证的必要性由服务端决定 | VerificationOption.captcha_required,不要从 kind 反推——策略在服务端,可以不发前端就改 |
| 验证码票一次性 | 解一次可换一个 captcha_pass(绑定该 ASN、约 30 分钟),重发挑战凭它跳过验证码 |
params 原样回传 | authorize-context 返回的已归一化参数,在 approve 时逐字节回传 |
| 会话令牌只进 sessionStorage | 安全红线:身份凭据必须随标签页消亡,不进 localStorage、不进 cookie、不进 URL |
dn42.auth.lastAsn 是唯一进 localStorage 的东西——ASN 是半公开的 registry 数据、 不是凭据,那条红线对它不适用。
端点全表见 api/auth.md。
3. 组件
| 文件 | 职责 |
|---|---|
App.svelte | 流程编排:boot → 校验请求 → 有无会话 → 验证身份 → 同意 |
Stepper.svelte | 四步指示器 |
StepAsn / StepMethod / StepAnswer / StepConsent | 四个步骤屏 |
PasskeyEnrollCard / PasskeyManager | passkey 注册与凭据管理 |
CodeInput / CopyBlock | 验证码输入、可复制块(PGP / SSH 挑战文本) |
ErrorScreen | 致命错误终屏 |
lib/errors.ts | 错误分类(classify)与文案 |
lib/webauthn.ts / lib/passkeys.ts | WebAuthn 编解码与登录 / 注册流程 |
人机验证控件来自共享库 $ui 的 CapCheck,解题器是 ui/vendor/cap/。
4. 主题与语言
- 语言:英 / 简中 / 繁中 / 日,页内切换,存
dn42.auth.lang。 - 主题:不盖
data-theme标记,直接跟随系统。这是一次性流程,没有导航也没有 设置入口。共享样式表为此备了第二条暗色投递路径,见 internals/i18n-and-theming.md。
5. 为什么不用 SvelteKit
它只有一个入口(/authorize)、没有路由也没有 SSR,一个 index.html 加一棵组件树 就够了;套上 SvelteKit 只会多一层 adapter 和 .svelte-kit 产物。
因此 $ui 别名在它自己的 vite.config.ts 里声明(另外两站由 sveltekit({ alias }) 注入)。
6. 部署
源站 nginx 同源静态托管,不是 Worker。产物 dist/ 与认证服务在同一个来源下, 前端始终用相对路径请求 /api/v1/*,因此零 CORS。
两条最容易踩的:
⚠️ 改了
ui/之后这个站不会自动重建。 另外三站被 Cloudflare 的 Build watch paths 覆盖,这个站不在 CF 上——必须手工npm run build:auth并重新投放。
⚠️
index.html里的两处__CSP_NONCE__占位符不能删。 nginx 靠sub_filter逐请求把它换成$request_id,同一个值也进 CSP 响应头。删了验证码会静默超时。
发布顺序、自检与验收见 guides/deploy-auth.md。 托管形态的完整理由见 internals/auth-hosting.md。
7. 本地开发
bash
npm run dev:auth # http://localhost:5173vite.config.ts 把 /api 反代到 https://auth.natlan.io,与线上的同源布局一致—— 前端代码始终用相对路径,本地也没有 CORS,不需要为开发切基址。指向另一套部署时用 VITE_AUTH_API。
vite dev 下没有东西替换 __CSP_NONCE__,也不发 CSP,所以那个占位符在开发时是惰性的。