---
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
```
