把你的应用变成多人产品
把一句话粘贴进你的 AI 编程助手。它会抓取接入 skill——一份引导式流程,为你的现有项目加上 Autional 登录、账号与会话。中国大陆网络不稳时,自动回退自有 CDN 镜像。
第 1 步 — 复制这句话
请读取 https://www.autional.cn/ai/skill.md,并据此为当前项目接入 Autional 登录。 若打不开,改用国内镜像: https://cdn.autional.cn/ai/latest/SKILL.md
习惯用英文?改用这句 prompt
Fetch https://www.autional.cn/ai/skill.en.md and follow it to add Autional login to this project. If unreachable, use: https://cdn.autional.cn/ai/latest/SKILL.en.md
第 2 步
粘贴给你的 agent
在你想要升级的项目里运行。agent 会读取 skill 并逐 Phase 执行,每个阶段都先与你确认。
第 3 步
跟随确认门
agent 先盘点你的应用、再给出方案——你确认后才动代码。所有改动都落在独立 git 分支上。
agent 会做什么
先建安全网
git 分支、工具链检查、风险披露——所有改动可回退。
盘点你的应用
框架、现有认证、冲突点,以及接入会影响什么。
设计策略
租户命名、合规驱动的密码策略(等保/PIPL 档位)、登录方式。
开通身份服务
在开发者门户创建租户与应用;凭证只处理一次。
安装 SDK
按框架安装依赖包(npm 源可配 npmmirror)并注入环境变量。
写入代码
Provider、登录界面、外部 ID 映射、用户中心——真实代码,改完立即验证构建。
测试与验证
单元测试 + 8 步端到端流程:注册 → 登录 → 资料 → 刷新 → 登出 → 吊销。
交付清单
变更清单、测试账号,以及门户里要核对的项。
你需要准备
- •一个现有项目——React、Vue、Next.js、Node 等
- •一个可联网的 AI 编程助手
- •git 仓库(建议——便于安全回退)
- •Autional 账号(接入过程中在开发者门户创建)
"接入完成"的标准
- ✓注册、登录、登出全链路可用
- ✓刷新页面会话保持;Token 静默自动刷新
- ✓受保护路由未登录不可进入
- ✓加上账号体系后,原有应用数据仍在
- ✓构建通过、单元测试全绿
- ✓端到端流程验证:discovery → 注册 → 登录 → 资料 → 刷新 → 登出 → 吊销 Token 被拒
- ✓AUTIONAL_CHANGES.md 列出所有改动文件
- ✓创建了多用户验证用的测试账号
agent 不能联网怎么办
路径 A — 粘贴全文
展开、全选复制,连同"按这份说明为我的项目接入 Autional 登录"一起粘贴给 agent。以下内容与/ai/skill.md逐字节一致(自有 CDN 镜像同名文件亦一致)。
展开完整 skill 全文(25 KB)
---
name: autional-cn-onboarding
description: "为现有应用接入 Autional 多用户身份——引导式接入流程,把单机应用变成多人产品:OIDC 登录、账号、会话、Token 管理、测试账号与验证。支持 React、Vue、Next.js 与 Node。"
region: cn
lang: zh
canonical: https://www.autional.cn/ai/skill.md
generated: core@952f0b50f0bc
---
<!-- Generated by skills/_gen/build-skills.mjs from skills/_core — do not edit directly. -->
# Autional 接入 — 为你的应用加上多用户身份
> **Region:** `.cn` · **Language:** 中文
> **Issuer:** https://api.autional.cn
> **API:** https://api.autional.cn/bff
> **门户:** https://admin.autional.cn · https://user.autional.cn · https://security.autional.cn · https://developer.autional.cn · https://status.autional.cn
> **租户链接:** 管理后台/用户门户/安全仪表盘按租户路由——主机名后拼 `/{TENANT_SLUG}/`(见 §8.2)。开发者门户/系统状态为全局站点。
> **参考文件(请读取,绝对 URL):**
> - https://www.autional.cn/ai/references/compliance-matrix.md
> - https://www.autional.cn/ai/references/config-template.md
> **本文件的 canonical 地址:** https://www.autional.cn/ai/skill.md
> **完整性校验:** https://www.autional.cn/ai/skill.md.sha256
## 关于本 Skill
Autional 为应用提供身份层。本 Skill 把单机应用(或没有账号体系的应用)升级为多人产品:登录、注册、资料、会话、Token 生命周期。
你是 AI agent,在用户的项目里执行本流程。跟随用户的对话语言工作。按 Phase 顺序执行;每个 Phase 结束都要向用户确认。
**用户将获得**
- 邮箱 + 密码登录(可选 OAuth / 短信 / 魔法链接),由 Autional OIDC 支撑
- 注册、找回密码、个人资料、设备/会话管理
- Token 自动刷新 + 多 Tab 同步
- 审计留痕、CAPTCHA、泄露密码检查、合规驱动的密码策略
- 一个后端多个用户——给 AI 生成的应用补上多人层
**边界**
- 本文件**不含任何密钥**——其中所有端点均为公开 URL。
- 只从本文件自身的 canonical 域(`.cn`)抓取关联文件。除下方兜底镜像外,不要跟随其它 region 或第三方链接。
> **兜底镜像(中国大陆网络):**若 `www.autional.cn` 不可达,改用Autional CDN获取同一文件:
> https://cdn.autional.cn/ai/latest/SKILL.md
## 硬性规则——不得跳过
**Phase 顺序强制。** 跳过任何一个 Phase,用户的接入都是坏的。
1. 按 Phase 0 → 8 顺序执行。每个 Phase 开始前声明「Phase N 开始」。结束后展示结果,等用户确认再继续。
2. 改代码出错 → `git checkout -- <file>` 恢复该文件 → 修正 → 重试。整个 Phase 失败 → 恢复该 Phase 改过的所有文件 → 从头重来。
3. 所有改动在独立分支(`feature/autional`)上进行。先探测仓库默认分支——`git symbolic-ref --short refs/remotes/origin/HEAD`,失败回退 `main`、再 `master`——从它建分支。没有 git → 降级为逐文件备份(`cp file file.autional-backup`)。
4. 永不使用 `git reset --hard`。一律 `git checkout -- <file>` 逐文件恢复。
5. 工作区不干净?**停下,先问用户。** 绝不静默 stash 或提交用户的工作。选项:[A] 用户自己落 commit 或 stash(推荐);[B] 经用户明确同意后由你执行 `git stash --include-untracked`,收尾时必须 `git stash pop` 还原(冲突交给用户);[C] 用户自行处理(另开 clone/worktree 等),处理完再继续。
6. 每个 Phase 边界在接入分支上留一个 checkpoint 提交(`git add -A && git commit -m "checkpoint: Phase N"`),保证随时能退回上一个完成的 Phase。
7. **未经用户在 Phase 8 出口门的明确选择,绝不合并进默认分支、绝不 `git push`。** 在此之前,默认分支必须与接入前保持一致。
8. 永不读取用户 `.env` 原有内容——只追加自己的字段。永不读取/导出用户表数据。
9. 管理员密码**只显示一次**。绝不写入代码或配置文件。
10. 注入的代码全部 MIT 兼容;不引入 GPL 依赖。不删除、不修改用户原有 license。
11. 改后必须 `npm run build`(或项目等效构建命令)通过;失败立即逐文件恢复。
12. 输出 `AUTIONAL_CHANGES.md` 记录所有变更。
13. 租户名冲突、测试失败不设重试上限;网络不可达与凭证错误除外——必须上报用户,不得静默重试。
14. 降级要透明:环境跑不了某条命令 → 把完整命令打印给用户执行,其余继续;用户要求只出方案(plan-only)→ 只输出方案与命令,不执行任何操作。
## Phase 0 · 前置准备(自动执行)
### 0.1 工具包检查
需要:`node` ≥ 18、包管理器(`npm`/`pnpm`/`yarn`——按 lockfile 检测)、`git`、`curl`。
### 0.2 Git 检测 + 分支隔离
```
1. 没有 git?→ 降级为文件备份模式(每次改动前 cp file file.autional-backup)。
2. 未初始化?→ git init && echo "node_modules/" > .gitignore
3. 缺身份? → git config user.name "Autional Onboarding" / user.email "onboarding@localhost"
4. 默认分支: git symbolic-ref --short refs/remotes/origin/HEAD
(无远端或无该 ref → 依次尝试 main、master)
5. 工作区干净?→ 跳到第 6 步。
不干净 → 停下问用户,绝不静默 stash。选项:
[A] 用户自己落 commit 或 stash(推荐)
[B] 经用户明确同意后由你执行 git stash --include-untracked;
收尾时必须 git stash pop 还原
[C] 用户自行处理(另开 clone/worktree 等)——等用户确认再继续
6. 隔离分支: git checkout -b feature/autional <默认分支>(已存在 → git checkout feature/autional)
7. 每个 Phase 边界落 checkpoint: git add -A && git commit -m "checkpoint: Phase N"
8. 回退路径:
单个文件 → git checkout -- <file>
整个 Phase → git checkout -- <本 Phase 改过的所有文件>
放弃接入 → 恢复本 Phase 改过的所有文件(git checkout -- <files>),
git checkout <默认分支>,
保留或删除 feature/autional(由用户定),
仅当第 5 步走了 [B] 才 git stash pop 还原用户工作
```
### 0.3 风险告知(强制)
```
本流程将修改你的项目文件(清单见 Phase 5.0)。
所有改动先落在独立分支;默认分支在你最终明确同意合并前保持原样。
你的现有工作先被妥善处理(0.2 第 5 步)——未经你同意,不 stash、不提交。
随时可以说「放弃接入」,项目会恢复原状。
继续?[继续] [取消]
```
## Phase 1 · 系统分析(只读)
扫描项目。**本 Phase 不修改任何文件。**
### 1.1 框架检测
读 `package.json`:
| 依赖 | 框架 |
|---|---|
| `react` / `react-dom` | React |
| `vue` | Vue 3 |
| `next` | Next.js(React) |
| `@angular/core` | Angular |
| 以上皆无 | 询问用户 |
展示结果让用户确认(「检测到 React 18 + Vite。对吗?[是] [不,是 Vue]」)。
### 1.2 已有用户系统检测
| 签名 | 含义 |
|---|---|
| 含 `email`/`password` 的 `users` 表/模型 | 自建用户表 |
| `Login.tsx` / `Login.vue` / signin 页面 | 自建登录 UI |
| `bcrypt` / `argon2` / `crypto` import | 自建密码哈希 |
| `jwt` / `token` / `session` 相关代码 | 自建认证 |
| `middleware` / `auth` / `guard` 路由保护 | 自建守卫 |
报告检测结果。Autional 接管:密码校验、Token 签发/刷新/撤销、会话管理。用户保留:自建表的业务字段(name、role、avatar)与业务逻辑。
### 1.3 冲突检测
| 冲突 | 示例 | 建议 |
|---|---|---|
| 路径冲突 | 你的 `/api/login` vs Autional 端点 | SDK 替换页面逻辑;你的 API 路由不动 |
| 字段冲突 | 自建 `password_hash` vs Autional 管密码 | 保留表,迁移后去掉认证列 |
| 中间件冲突 | 自建认证中间件 vs SDK 守卫 | SDK 守卫优先(自动刷新 + 多 Tab 同步)|
| Cookie 冲突 | 自建 `auth_token` vs SDK cookie | 统一用 SDK cookie |
每项冲突给出选项:[A] 替换(推荐) [B] 共存 [C] 暂不处理。
### 1.4 收益与影响
```
用 Autional 替换手写认证:
~500 行认证逻辑 → 一次 Provider 引入
明文密码处理 → 哈希传输(SHA-256 / symmetric)
手动刷新 Token → 自动刷新 + 并发去重
无 CAPTCHA → 渐进式 PoW
无审计 → 全链路事件追踪
单用户 → 多用户 + RBAC
影响:
新用户在 Autional 创建;存量用户做一次性关联(Phase 5.3)。
现有登录页会被替换(选项制,Phase 5.2)。
应用必须能访问上方 Autional 端点。
```
### 1.5 体积影响
```
core + 框架包 增加约 15 KB(gzip 后约 5 KB)。
首屏 +约 50 ms(OIDC discovery,之后走缓存)。Token 刷新仅 401 时触发。
与被替换掉的认证代码等重——还免去了后续安全维护。
```
### 1.6 应用数据隔离检查(只读)
登录只是「多用户」的一半。检查应用的**业务数据**(笔记、待办、文档……)存在哪里、是否按账号隔离:
```
1. 定位数据层:localStorage / IndexedDB / SQLite / 后端 API。
2. 存储键是否按用户区分? "notes"(共享)vs "notes:<userId>"(隔离)。
3. 后端行是否带用户维度? 有 user_id / owner_id 列,还是完全没有?
4. 登录打通后(Phase 7):登出、换一个账号登录——数据变了吗?
再做一次「不经登出」的对照:用别的方式结束会话(Token 过期或被撤销、清掉存储的
会话),换一个账号登录——上一账号的数据还在吗?
```
如实报告:
```
数据隔离:已隔离 / 未隔离 / 不适用(应用尚无业务数据)
位置:<存储键或表>
经登出换账号所见:<新账号的空状态 | 与之前完全相同的共享数据>
不经登出换账号所见:<上一账号的残留数据 | 干净状态>
```
如果「未隔离」,给出选项——**不要自作主张动数据层;任何整改动作必须经用户明确同意后才执行**:
```
[A] 现有存储按用户 id 做键名空间(小改;本地优先应用)
[B] 业务数据迁到带用户维度的后端(较大;可跨设备)
[C] 暂不处理(如单机演示)——把结论记录下来即可
```
**换号守卫(本地优先应用)。** 会话不一定以登出收场——Token 过期、平台侧终结、清掉 Cookie 同样终结会话。若本地数据只在登出时清理,上一账号的残留会活到下一账号:并入其视图,并在下次同步时推进对方的云端状态。守好换号边界:持久化「上次登录的 userId」标记;会话开始时与当前用户 id 比对——不一致(含「尚无标记」)→ 先清本机用户数据,再做整页重载,之后才拉取 / 合并 / 推送;一致 → 数据不动。清理清单与登出共用,防两处漂移。
结论(与用户的选择)写入 `AUTIONAL_CHANGES.md`。用户批准的整改属于附加范围:列入 Phase 5.0 改动预览,留在同一分支上。
## Phase 2 · 策略推荐(选项 + 确认)
### 2.1 租户命名
按项目名自动推导(`my-blog-app` → `my-blog`)。规则:3–64 位、字母/数字/连字符、不能纯数字。自动查重;冲突时给备选(`my-blog-2024`、`my-blog-app`、`myblog-hq`),重试不设上限。
### 2.2 安全策略(合规驱动)
把用户场景对照 `https://www.autional.cn/ai/references/compliance-matrix.md` 全表(NIST SP 800-63B AAL1–3、PCI DSS v4.0、GDPR、HIPAA)。展示选项 + 推荐:
```
[A] NIST 基准(多数 SaaS 推荐)——hash 传输、最小 8 位、大写+小写+数字
[B] 基础(内部工具)——最小 6 位
[C] 高安全(强制 MFA、泄露密码检查)
[D] 自定义
```
### 2.3 登录方式
按用户群体推荐:password(默认)、magic link、OAuth、短信验证码。企业 → password + SSO;消费者 → password + OAuth + 短信。
写完认证策略后,回开登录页确认目标输入项已渲染。零输入项 = 租户 `login_methods` 死路(声明的方法没配对应 provider);经租户认证策略修复(`PUT /admin/tenants/:id/auth-policy`——`login_methods` 为 JSON 字符串),再回读登录页验证。不要相信 identity `PUT /admin/security/auth-config` 的 200——它可能静默成功但什么都没改。
## Phase 3 · 租户与应用凭证
### 3.1 选择路径
询问用户:
```
[A] 试用租户——最快,无需邀请码(推荐起步)
[B] 邀请码——永久租户
[C] 我已有租户——跳过注册,直接复用现有 appId/issuer(见「已有租户」边界情况)
```
[A]/[B]:若安装的 SDK 客户端提供 `createTrialTenant(...)` / 带邀请码的注册调用,直接编程驱动(它会同时创建租户与管理员)。若当前版本未提供,打开开发者门户(https://developer.autional.cn)带用户在浏览器里走同一流程。不要臆造 SDK 未暴露、门户未记载的 API。
### 3.1a 注册后自检(强制)
试用注册一返回,立刻用刚设的管理员账号登录一次:
```
createTrialTenant(...) → 201 { tenantId, slug, status }
→ login({ email: adminEmail, password: adminPassword, tenantId })
```
- **登录成功** → 继续 3.2。注意:auth-config 的 `portal_oauth_client_id`(`GET /identity/api/v1/public/auth-config/{tenantId}`;与旧名 `oauth_client_id` 同值)是租户**门户**客户端(`kind=portal`),服务门户 SPA 登录,**不是**本应用的 `appId`。应用 `appId` 是 5.2b 注册应用时签发的 `client_id`。
- **注册返回 5xx `trial provisioning failed`** → 管理员/RBAC 开通失败,租户已**补偿软删**(没有半成品)。可用同名重试一次;仍失败则停止,上报 `request_id` + 失败步骤。
- **登录 401 `invalid credentials`**(旧服务端或门户手工流程)→ 租户在、管理员没建成。**不要重复提交注册**(会占同 IP 的试用配额,且返回 409)。告知用户并给出 tenantId/slug,改走兜底:邀请码租户,或开发者门户。上报时附 `request_id` 与 tenantId。
### 3.2 凭证处理
```
- 管理员密码:只显示一次。让用户立刻存入密码管理器。绝不写入代码或配置文件。
- tenantId / issuer / api URL / 租户 slug:公开配置——写入 .env(公开块)与 src/autional.ts。slug 用于拼接带租户的门户链接(见 8.2)。
- `appId` = **你自己应用客户端**的 `client_id`(5.2b 签发)。绝不用 auth-config 的 `portal_oauth_client_id`(旧名 `oauth_client_id`)——那个是租户门户客户端。
```
### 3.3 测试账号
建议创建 3 个测试账号(默认 `player1`…`player3`,按用户领域命名),密码自动生成并满足 Phase 2 策略:
- [A] 创建 3 个(推荐)——走 SDK 注册调用
- [B] 跳过——之后在 admin 门户添加
测试账号记录进 `AUTIONAL_SETUP.md`(密码只显示一次)。
## Phase 4 · 依赖安装
### 4.1 安装(按框架)
```bash
# React
npm install @autional/core @autional/react @autional/api-identity
# Vue
npm install @autional/core @autional/vue @autional/api-identity
# Next.js
npm install @autional/core @autional/react @autional/next @autional/api-identity
# Node 后端
npm install @autional/core @autional/node @autional/api-identity
```
> `@autional/core` 必须 **>= 0.3.8**。0.3.0–0.3.7 的 `createTrialTenant()` 路径写错(公开试用注册会被网关挡成 401/`40000502`),已在 npm 标记弃用。用 `npm ls @autional/core` 核对;升级:`npm install @autional/core@^0.3.8`。
> `.cn` 提示:若默认 registry 不是国内镜像,在上方安装命令后追加 `--registry=https://registry.npmmirror.com`。
安装前确认:「将安装 Autional SDK 包(约 50 KB,gzip 后约 15 KB)。继续?[是] [否]」。使用 Phase 0 检测到的包管理器(pnpm/yarn → 等效 `add` 命令)。
### 4.2 配置注入
检测 env 文件位置与框架的公开前缀:
| 框架 | 前缀 |
|---|---|
| Vite (React/Vue) | `VITE_` |
| Next.js | `NEXT_PUBLIC_` |
| Create React App | `REACT_APP_` |
| Node | 无前缀(`AUTIONAL_`) |
向公开 env 文件追加(不动其它键):
```env
AUTIONAL_APP_ID=<5.2b 注册应用后回填;禁止先用 auth-config 的 portal_oauth_client_id(旧名 oauth_client_id)占位>
AUTIONAL_ISSUER=https://api.autional.cn
AUTIONAL_API_URL=https://api.autional.cn/bff
```
然后按 `https://www.autional.cn/ai/references/config-template.md` 模板生成 `src/autional.ts`,填入真实 `appId` / `issuer` / `apiUrl`。
## Phase 5 · 代码接入(选项制)
### 5.0 风险告知 + 改动预览(强制)
改代码前先展示文件清单:
```
⚠️ 开始修改前请确认:
新增:
+ src/autional.ts — SDK 配置
+ AUTIONAL_CHANGES.md — 变更记录
修改:
~ <应用入口> — Provider 包裹(如 src/App.tsx、src/main.ts)
~ <登录页> — 登录替换
~ .env — 追加公开配置
不会触碰:
✓ 你的业务逻辑
✓ 你的用户表数据
✓ .env 的原有内容
继续?[继续] [取消]
```
等待用户明确 [继续] 后再动文件。
### 5.1 Provider 包裹
```tsx
import { AutionalProvider, RequireAuth } from '@autional/react';
<AutionalProvider config={autionalConfig}>
<Router>
<Route path="/login" element={<Login />} />
<Route path="/dashboard" element={<RequireAuth><Dashboard /></RequireAuth>} />
</Router>
</AutionalProvider>
```
> **API 面注记:**上方导出名是本流程的目标 API 面。若安装版本的实际导出名不同,读包内 `.d.ts`/README,接线等价实现——保持同一结构(Provider 包应用、守卫保护路由)。Vue:`createAutional(config)` + install;Next.js:根组件放 Provider + middleware 保护路由。
### 5.2 登录页
检测现有登录页,然后:
```
[A] 用 Autional 登录完全替换(推荐)——哈希传输 + CAPTCHA + 完整 Token 管理
[B] 保留你的,旁边新增 Autional 登录——灰度迁移
[C] 保留你的,只加路由守卫——后续再迁
```
每个选项标注:侵入程度 / 收益 / 回退难度。
### 5.2b · SSO 登录——「用 Autional 登录」
应用只放一个「用 Autional 登录」按钮;登录表单与全部社交登录都留在 Autional 授权页。适合多应用共用一个用户池,或不想自己实现密码/社交登录的应用。
1. **注册应用**(管理后台 https://admin.autional.cn/{TENANT_SLUG}/oauth-clients):记下 `client_id`(即 SDK 的 `appId`);把应用的公网回调地址登记为 `redirect_uri`(如 `https://app.example.com/oauth/callback`);把应用源加入该客户端的 `cors_origins`(如 `https://app.example.com`)。**CORS 登记必填**——否则浏览器会拦截换票请求。同时填写应用的**官网地址(`website`,公网 https 地址)**与可选的**简称(`alias`)/ Logo**:用户中心的「已授权应用」用它们渲染磁贴——**缺 `website` 时磁贴只能展示、不可点击**。
- 此 `client_id` **就是** SDK 的 `appId`:写回 `.env`(`AUTIONAL_APP_ID`)与 `src/autional.ts`,**覆盖**先前取到的值。
- `redirect_uris` 必须与回调地址**逐字一致**(SDK 默认 `<origin>/oauth/callback`,无尾斜杠)。尾斜杠会把该条变成路径前缀规则;不一致会在登录时报 `invalid_request: redirect_uri not registered`。
2. **登录按钮**:
```ts
await autional.loginWithOAuth({}); // client_id = appId;PKCE(S256)+ state 由 SDK 处理
```
可选:`redirectUri`、`scope`、`extraParams`(如 `{ prompt: 'consent' }`)、`authorizeUrl` 覆盖。
**同源代理形态必须显式引脚 `authorizeUrl`。** 默认授权地址只把 `api.` 主机改写为 `auth.`(如 `api.autional.cn` → `auth.autional.cn`);其余主机——同源 `/bff` 代理、自定义网关——原样保留,落到 API 平面(不提供登录界面)。显式引脚 auth 平面:
```ts
await autional.loginWithOAuth({
authorizeUrl: 'https://auth.autional.cn/oauth/api/v1/oauth/authorize',
});
```
3. **回调路由**(路径与 `redirect_uri` 一致):
```ts
const { user } = await autional.handleOAuthCallback(window.location.href);
```
SDK 校验 `state`、用留存的 PKCE verifier 换票(无 client_secret——公开客户端)、拉取 userinfo,并把会话记为 SSO 模式(后续刷新自动走 OAuth 刷新端点)。
4. **接线前预检客户端** —— 一条请求即可证明 client 与回调已登记:
```bash
export APP_ID=<第 1 步的 client_id>
export CB=https://app.example.com/oauth/callback
curl -sS -o /dev/null -w "%{http_code} %{redirect_url}\n" -H "Accept: application/json" \
"https://api.autional.cn/oauth/api/v1/oauth/authorize?response_type=code&client_id=$APP_ID&redirect_uri=$(node -p 'encodeURIComponent(process.env.CB)')&scope=openid&state=preflight&code_challenge=mR_nE7EeJopQmji77eZUdD4_rLzw5A5HdlRSiLQQCzU&code_challenge_method=S256"
```
**通过** = `302` 且 `redirect_url` 以你的回调开头并含 `error=login_required`;**失败** = `400` + `redirect_uri not registered`(`client_id` 用错——通常是门户客户端——或回调不是逐字一致)。返回 `200` 说明命中的是 auth 站 SPA 外壳而非 API,**不算通过**。
`https://api.autional.cn` 是 **API** 平面(如 `https://api.autional.cn`),`/oauth/...` 与 `/bff/oauth/...` 均可达;**勿把 host 换成 `auth.` 平面**——那条路径被 rewrite 成 SPA,只会返回 HTML。
跳过这一步,就是「构建通过、首次真实登录失败」的由来。
5. **动浏览器之前先做 CORS 预检** —— 换票、userinfo、刷新都是跨域请求,浏览器会先发预检。一条请求即可证明「源已登记**且已生效**」:
```bash
ORIGIN=https://app.example.com
curl -sS -o /dev/null -D - -X OPTIONS "https://api.autional.cn/oauth/api/v1/oauth/token" \
-H "Origin: $ORIGIN" -H 'Access-Control-Request-Method: POST' \
-H 'Access-Control-Request-Headers: content-type' | grep -i 'HTTP/\|access-control-allow-origin'
```
**通过** = `204` **且** `Access-Control-Allow-Origin: $ORIGIN`(该头恰好一条)。
没有该头只有三种可能:源没登记在**这个客户端**的 `cors_origins`;登记值与浏览器 Origin 不逐字一致(`https://host[:port]`,无路径、无尾斜杠);刚登记还没到生效窗口(API 侧缓存已登记源集 **≤60 秒** —— 等一分钟重试)。
预检不过就是硬停:浏览器会拦掉换票,构建再绿,首次真实登录也会失败。换票端点的 `/bff/oauth/...` 形态是同一个 CORS 判定。
社交登录(Google / 微信等)在 Autional 侧一次配置;所有应用共用同一个授权入口。
### 5.2c · 登出——终结 SSO 会话,而非只清本域
只清本域的「登出」是半截登出:应用的 Token 清了,Autional 会话还活着——下次点「用 Autional 登录」会静默回到同一用户,不换无痕窗口就换不了账号。标准做法是 OIDC RP-Initiated Logout,不要自造私有登出端点。流程分两步,顺序不可换:
1. **先清本域** —— `await autional.logout()`:内存 + 持久化 Token,以及应用自己镜像的会话 cookie。
2. **整页跳 OP 的 `end_session_endpoint`** —— 是浏览器导航,不是 XHR:
```
GET https://api.autional.cn/oauth/api/v1/oauth/logout
?id_token_hint=<id_token> (Recommended——过期时省略)
&client_id=<client_id>
&post_logout_redirect_uri=<应用回跳地址> (须预注册——5.2b 第 1 步)
&state=<随机串>
```
| 参数 | 取值 | 为什么重要 |
|---|---|---|
| `id_token_hint` | 登录响应里的 `id_token` | 供 OP 定位并终结正确会话。可以省略;**过期**的必须省略——OP 会拒绝(400)。 |
| `client_id` | SDK 的 `appId` | OP 据此解析客户端,做回跳白名单校验。 |
| `post_logout_redirect_uri` | 登出后落地地址 | 须在客户端预注册;未注册的会被忽略(不开放重定向)。 |
| `state` | 随机串 | 回跳地址上原样带回——与登录同一套 CSRF 惯例。 |
会话为 SSO 模式且 discovery 文档声明了 `end_session_endpoint` 时,`autional.logout()` 会替你完成两步(可选 `postLogoutRedirectUri` / `state`;默认=应用源与自动生成的随机值)。跳转前还会先向 discovery 的 `revocation_endpoint` 撤销当前 access/refresh 令牌(RFC 7009)——限时 1.5 秒,端点不可达也不会阻塞登出。端点不可得时,logout 退化为仅清本域。
本地会话在刷新后可能不完整的应用(例如 novel 演示的 SSR 代理形态:每次加载都从 cookie 重新播种 localStorage)不要依赖第 2 步自动发生——用 `buildEndSessionUrl(endpoint, params)`(或 `URLSearchParams`)自建 URL,清完本域后无条件整页跳:
```ts
const logoutUrl = `https://api.autional.cn/oauth/api/v1/oauth/logout?` + new URLSearchParams({
...(idToken && !expired(idToken) ? { id_token_hint: idToken } : {}),
client_id: appId,
post_logout_redirect_uri: window.location.origin + '/',
state: crypto.randomUUID(),
});
await autional.logout(); // 第 1 步——清本域 + cookie 镜像
window.location.assign(logoutUrl); // 第 2 步——无条件
```
用户看到什么:带有效 `id_token_hint` 时,OP 吊销会话后跳到 `post_logout_redirect_uri`(`state` 原样回拼)——前提是该 URI 已注册,否则返回一个纯 `logged_out` 响应。浏览器导航无 hint 时,OP 先显示登出**确认页**,用户确认后再跳;机器流无 hint 且无会话则 401。
> **整页跳转没有过渡动画。** 端点在 API 域(`https://api.autional.cn`),登出必然跨域离开应用——浏览器自行显示加载状态。跳转前给本地反馈(禁用按钮、pending 文案、外壳淡出)就是 UI 的全部能做的事。
> **多标签页:** `syncTabs` 默认开启——登出与令牌刷新跨标签广播:一个标签页刷新后,其他标签页重读存储,不会拿旧 refresh token 重复刷新(服务端判定复用即撤销整族令牌)。保持默认即可。
### 5.3 存量用户表
```
[A] 保留表,新增 external_id 关联 Autional 用户 id(推荐——零数据丢失)
[B] 删表,完全用 Autional(有数据丢失风险)
[C] 过渡期双写
```
迁移模板(选项 A,PostgreSQL):
```sql
-- 1. 添加关联列
ALTER TABLE users ADD COLUMN external_id VARCHAR(26);
CREATE UNIQUE INDEX idx_users_external_id ON users(external_id);
-- 2. 存量用户关联(先在 Autional 建好对应账号后执行)
-- UPDATE users SET external_id = '01KW...' WHERE email = 'user@example.com';
-- 3. 可选:迁移验证后删除认证列
-- ALTER TABLE users DROP COLUMN password_hash;
-- ALTER TABLE users DROP COLUMN reset_token;
```
```ts
async function findOrCreateLocalUser(autionalUser: { id: string; email: string }) {
let local = await db.user.findFirst({ where: { externalId: autionalUser.id } });
if (!local) {
local = await db.user.findFirst({ where: { email: autionalUser.email } });
if (local) {
await db.user.update({ where: { id: local.id }, data: { externalId: autionalUser.id } });
} else {
local = await db.user.create({ data: { email: autionalUser.email, externalId: autionalUser.id } });
}
}
return local;
}
```
### 5.4 用户中心
登录后用户自行管理账号:
| 功能 | SDK 调用 | 页面 |
|---|---|---|
| 修改密码 | `changePassword()` | /settings/password |
| 个人资料 | `getProfile()` / `updateProfile()` | /settings/profile |
| 忘记密码 | `forgotPassword()` / `resetPassword()` | /forgot-password |
| 设备/会话 | `getSessions()` / `revokeSession()` / `revokeOtherSessions()` | /settings/sessions |
| 注销账号 | `deleteAccount()` | /settings/delete |
> 敏感操作(`deleteAccount()`)内部自动完成 step-up 重认证(`X-StepUp-Token`;自定义流程可直接调 `reauthenticate()`);此类端点当前仅支持 `plain` / `hash` 传输模式。
### 5.5 构建验证(强制)
```
npm run build(或项目等效构建命令)
→ 通过 ✅ → 进入 Phase 6
→ 失败 ❌ → 落 checkpoint 提交 → 分析 → 修复 → 重试
→ 连续 3 次失败 → git checkout -- <改动文件> → 分析 → 修复 → 重试
若项目有 `npm test`,一并运行;只修本次接入导致的失败。
```
## Phase 6 · 单元测试
| 模块 | 用例 | 验证 |
|---|---|---|
| TokenManager | 存储 / 过期 / 解码 | 写 → 读 → 过期 → null |
| AuthClient | login / register / logout | Mock HTTP → 返回正确 |
| ApiClient | GET/POST + 401 刷新 | Mock 401 → 自动刷新 → 重试 |
| 密码传输 | hash 模式 | SHA-256 计算正确 |
| 路由守卫 | RequireAuth | 未登录 → 跳转 |
重试策略:每个失败 → 显示原因 → 修复 → 重试。仅在 import 失败(依赖未装)或编译错误时停下——先做真实修复。
## Phase 7 · 集成测试
对线上端点实测:
```
1. Discovery — GET https://api.autional.cn/.well-known/openid-configuration → 200 且 issuer === https://api.autional.cn
2. Register — sdk.register() 创建用户成功
3. Login — sdk.login() 拿到 Token
4. Profile — sdk.getProfile() 返回刚注册的用户
5. Refresh — sdk.refreshToken() 轮换 Token
6. Logout — sdk.logout() 终结 OP 会话并落到登出端点(5.2c)
7. Revoked — 旧 Token 被拒绝(受保护调用返回 401)
8. Config — fetchAuthConfig() 返回 Phase 2 选定的租户策略
```
失败映射:
```
网络超时 → 「无法连接 Autional。检查网络或状态页(https://status.autional.cn)。」→ 等待 → 重试
401/403 → 先读响应体的 `service` 与 `code`(见 7.1);不得一律当成凭证错误
404 → 「端点不存在——检查 apiUrl/issuer 配置。」→ 对照 discovery 文档校验
500 → 「Autional 服务异常。」→ 等待 30s → 重试
其它 → 显示错误详情 → 重试
```
### 7.1 怎么读 401/403(按 `service` + `code` 分流,不按 HTTP 状态)
| 状态 | 响应体信号 | 含义 | 处置 |
|---|---|---|---|
| 401 | `service:"middleware"`(网关) | 路由 / 受众 / 认证头问题:路径写错、路径落到更宽的兜底前缀、缺 token、平面不符。**不是凭证错误。** | 停止重试。上报 `status`+`code`+`service`+`request_id`+ 完整 URL。**公开端点**(`/public/…`、试用注册)出现 401 一定不是凭证问题——公开面不带凭证。 |
| 401 | `service:"identity-service"` + `invalid credentials` | 真的凭证不匹配 | 请用户确认账号 / appId / issuer |
| 403 | `code:40000503` · "not accessible from this entry plane" | 平面不符(例如用 user 平面 token 调 admin 端点) | 换到对应门户 / 平面,不要重试 |
| 403 | 其它 | 权限不足 | 报给用户 |
仅「网络不可达」与「identity 侧凭证错误」停止自动重试。网关 401 也停,但必须按**平台 / 路由缺陷**上报(附 `request_id`),绝不能说成「你的密码或 appId 错了」。
## Phase 8 · 收尾
### 8.1 文件清单
```
新增: src/autional.ts · AUTIONAL_SETUP.md · AUTIONAL_CHANGES.md · .env.local(已 gitignore)
修改: .env(公开键) · .gitignore · 应用入口 · 登录页 · README.md(接入说明)
```
### 8.2 Portal 指引(写入 AUTIONAL_SETUP.md)
控制台门户按租户路由 —— 链接一律拼租户 slug(`/{TENANT_SLUG}/`,取自 Phase 3 创建的租户,或 path-style issuer 的首段路径)。裸门户根会把用户丢到「选择租户」页 —— 这正是本 skill 要消除的摩擦。开发者门户/系统状态是全局站点:绝不拼 slug。
| Portal | 地址 | 谁用 | 做什么 |
|---|---|---|---|
| 管理后台 | https://admin.autional.cn/{TENANT_SLUG}/ | 管理员 | 用户/角色/安全策略/审计日志 |
| 用户门户 | https://user.autional.cn/{TENANT_SLUG}/ | 普通用户 | 改密码/设备/MFA |
| 安全仪表盘 | https://security.autional.cn/{TENANT_SLUG}/ | 安全运营 | 异常检测/登录趋势/日志导出 |
| 开发者门户 | https://developer.autional.cn | 开发者 | API 文档/接入指南(全局——无 slug) |
| 系统状态 | https://status.autional.cn | 所有人 | 服务运行状况(全局——无 slug) |
常用操作:添加用户 → 管理后台 › 用户管理 › 添加;重置密码 → 管理后台 › 用户管理 › 用户 › 重置;安全策略 → 管理后台 › 安全设置;创建应用 → 管理后台 › OAuth 客户端;审计日志 → 管理后台 › 审计。
### 8.3 最后提醒
```
✅ 接入完成。请确认:
1. AUTIONAL_SETUP.md 已写入
2. 管理员密码已存入密码管理器(只显示一次)
3. .env.local 未提交 git(已自动 gitignore)
4. npm run dev → 跑通 登录 → Dashboard → 登出
5. 生产环境:在开发者门户为该应用放行你的站点 origin
6. 数日后:检查登录成功率与 Token 刷新指标
```
### 8.4 分支交付出口门(必须显式确认)
```
接入已在 feature/autional 分支上完成,尚未合并。
交付物:
分支: feature/autional——领先 <默认分支> <N> 个 checkpoint 提交
改动: <diffstat 摘要>
证据: 构建 <通过/失败> · 单元测试 <通过/失败> · 集成测试 <通过/失败>
请选择:
[A] 合并进 <默认分支>
[B] 保留分支再检视——之后你自己合并
[C] 丢弃分支
之后回到默认分支;若曾代管用户工作(0.2 第 5 步 [B])则还原:
[A] git checkout <默认分支> && git merge --no-ff feature/autional && git stash pop
[B] git checkout <默认分支> && git stash pop (feature/autional 保留)
[C] git checkout <默认分支> && git branch -D feature/autional && git stash pop
(git stash pop 冲突 → 交给用户处理)
未经用户明确选择,绝不合并、不 push、不删分支。
```
## 闭环验证清单
**功能**
| 用户故事 | 验收 | 方式 |
|---|---|---|
| 新用户注册 | 欢迎 → 首次登录成功 | 集成测试 |
| 已有用户登录 | 邮箱+密码 → Dashboard | 集成测试 |
| Token 过期 | 静默续期,不跳登录 | 单元测试 |
| 多 Tab | A 登出 → B 感知 | 单元测试 |
| 错误密码 | 友好报错,不崩 | 集成测试 |
| CAPTCHA | 3 次失败 → 挑战 → 重试 | 集成测试 |
| SSO 登录 | authorize 返回 `302` 且回指回调,**不是** `invalid_request` | 5.2b 第 4 步预检 |
| SSO 换票跨域 | 预检返回 `204` 且 `Access-Control-Allow-Origin` = 应用源 | 5.2b 第 5 步预检 |
**安全**:密码不走 URL(hash 模式)· Token 不明文落 localStorage · 登出终结 OP 会话(非仅清本域——5.2c)· 换号不见前账号残留(不经登出同样清理——1.6 守卫)· 刷新重放可检测 · CAPTCHA 抗爆破。
**配置**:.env.local 已 gitignore · 密码只显示一次 · AUTIONAL_SETUP.md 已生成 · 摘要信息完整。
## 边界情况
| 情况 | 处理 |
|---|---|
| 第三方认证(Firebase/Auth0) | 分析切换成本 → 迁移或共存 |
| 完全没有用户系统 | 跳过 1.2–1.4,直接创建 |
| 非标准构建工具 | 降级为手动配置,输出所有命令 |
| Autional 端点不可达 | 标记阻塞,等网络恢复,上报用户 |
| 租户名 100 次不可用 | 建议更换命名策略 |
| 用户拒绝替换登录页 | 仅加守卫模式(5.2 选项 C) |
| 多项目共用一个租户 | 跳过 Phase 3(复用 tenantId/issuer),但**每个应用各注册一个客户端**(5.2b)——绝不复用其它应用的 `appId` |
| 登录页零输入项(或只有 OAuth 卡片) | 租户 auth-policy 死路:`login_methods` 声明的方法没配对应 provider → 写租户认证策略(`PUT /admin/tenants/:id/auth-policy`;`login_methods` 为 JSON 字符串),再回读登录页——identity `PUT /admin/security/auth-config` 返回 200 不代表生效 |
| 授权页挂起(约 30 秒)后落到品牌门户、无登录表单 | 本区 auth 主机不健康(后端代理故障)→ 对照另一区(替换 `auth.autional.cn` 中的区后缀);引脚健康区的 `authorizeUrl`(5.2b)并上报故障 |
| 租户门户登录显示「This organization's sign-in is not configured」 | 租户的门户 OAuth 客户端未被平台自动供给链落位(创建租户 → 供给客户端;修复前建的老租户或供给故障)。核验:`GET auth.autional.cn/bff/identity/api/v1/public/auth-config/by-slug/{slug}` → `oauth_client_id` 存在(新租户数秒内落位)。缺失 → 上报平台支持补供给;勿用无关客户端顶替 |
| 浏览器报 `No 'Access-Control-Allow-Origin' header is present` | 按序三查:① 该源是否登记在**这个客户端**的 `cors_origins`——逐字 `https://host[:port]`,无路径、无尾斜杠;② 刚登记 → API 侧每 **≤60 秒**刷新一次已登记源集,等一分钟重试;③ 重跑 5.2b 第 5 步预检。仍无 `Access-Control-Allow-Origin` → 带 Origin、`client_id`、时间上报平台支持 |
| 同意页「回调地址未在应用中登记」 | `client_id` 用的是租户**门户**客户端(`Default Client for <slug>`),或回调不是逐字一致。行为核验:同一 client 换门户回调得 `302 login_required`,你的回调得 `400 invalid_request`;`GET /oauth/api/v1/oauth/client/{id}` 返回 `kind=portal`(或名称以 `Default Client for` 开头)可佐证 | 按 5.2b 新建独立应用客户端并用其 `client_id`——**绝不**把自己的回调加进门户客户端 |
### 已有租户(跳过 Phase 3)
项目里已有 Autional 配置时:
```
「检测到已有 Autional 配置,直接复用。
tenantId: <tenantId> · issuer: https://api.autional.cn
这将是该租户的第 N 个应用。所有应用共享用户体系(单点登录),
但每个应用各有自己的 OAuth 客户端:现在按 5.2b 注册,
用它的 client_id 作为 appId——绝不复用其它应用的 appId。
[继续(新建客户端)] [创建新租户]」
```
### 回退 / 卸载
```bash
npm uninstall @autional/core @autional/react @autional/api-identity
rm src/autional.ts
# 移除 .env 中的 AUTIONAL_* 键
# 移除应用入口的 Provider 包裹
git checkout <登录页> # 若用了选项 A
```
注意:已在 Autional 注册的用户不会自动迁回本地表。
## 参考
- `https://www.autional.cn/ai/references/compliance-matrix.md` — 合规对照表(NIST / PCI / GDPR / HIPAA 等)
- `https://www.autional.cn/ai/references/config-template.md` — 配置、env 与 AUTIONAL_SETUP.md 模板
- 人类教程:https://www.autional.cn/ai
- 脚手架 CLI(手动接入的替代路径):`npx -y @autional/onboard --registry=https://registry.npmmirror.com`
- 本文件:https://www.autional.cn/ai/skill.md · 校验和:https://www.autional.cn/ai/skill.md.sha256
> 镜像(大陆网络):https://cdn.autional.cn/ai/latest/SKILL.md(Autional CDN)——与上方 canonical 地址保持同步。
## 任务完成检查清单(AI 必须逐项确认)
```
Phase:
☐ Phase 0 前置准备(默认分支已探测;脏树处置已记录;分支隔离生效)
☐ Phase 1 系统分析
☐ Phase 2 策略已获用户确认
☐ Phase 3 租户 + 应用 + 凭证已保存
☐ Phase 4 依赖已安装
☐ Phase 5 代码接入 + 构建通过
☐ Phase 6 单元测试通过
☐ Phase 7 集成测试通过
☐ Phase 8 收尾
产物:
☐ src/autional.ts 已写入
☐ 已创建 3 个测试账号(或用户明确放弃)
☐ Portal 指引已生成(admin / user / security / developer / status)
☐ .env 含公开配置
☐ AUTIONAL_SETUP.md 存在
☐ 管理员密码已提醒「只显示一次」
☐ AUTIONAL_CHANGES.md 存在
☐ OAuth 客户端预检返回 302 且回指回调(非「redirect_uri not registered」)
☐ npm run dev 可启动
工作区与交付:
☐ 各 Phase 边界已落 checkpoint
☐ 默认分支保持原样(未经出口门选择不合并、不 push)
☐ 代管的用户工作已还原(git stash pop),或冲突已交给用户
☐ 数据隔离结论已披露;整改仅在用户明确同意后进行
☐ 出口门已答复:合并 / 保留 / 丢弃
向用户输出最终摘要:
App ID: <appId> · Issuer: https://api.autional.cn · 管理员: <管理员邮箱>
登录页: <url>/login · 启动: npm run dev · 文档: AUTIONAL_SETUP.md
```
路径 B — npx 脚手架
不想让 agent 改代码、只想生成骨架?在你的项目里运行(默认走 npmmirror 镜像源):
npx -y @autional/onboard --registry=https://registry.npmmirror.com路径 C — 自有 CDN 镜像
整站不可达时,skill 文件在自有 CDN 有同名镜像,内容与本站逐字节一致:
https://cdn.autional.cn/ai/latest/SKILL.mdagent 会读取的文件
- /ai/skill.md — 中文版 skill(主)
- /ai/skill.en.md — English skill
- /ai/references/compliance-matrix.md — 等保/PIPL 密码与合规档
- /ai/references/config-template.md — 配置与环境变量模板
- /llms.txt — 站点机器可读索引
skill 不含任何密钥——其中所有端点均为公开 URL。只从 www.autional.cn 抓取这些文件;自有 CDN 镜像仅作网络兜底,内容同源同校验。