统一登录中心(Hub)接入指南:从创建应用到跑通 OAuth

统一登录中心(Hub)接入指南:从创建应用到跑通 OAuth#

本文面向“接入方 SaaS”(app1.com/app2.com…),目标是在**本项目(Hub)**中完成:

  1. 创建 SaaS Application(Hub 业务概念)
  2. 创建 OAuth Client(Better-Auth OIDC Provider 依赖的 oauth_application
  3. 走通 OAuth 2.1 授权码 + PKCE 的完整流程:authorize → consent/login → callback(code) → token

如果你需要更底层的实现解释,请同时参考:

  • docs/oauth-implementation-guide.md
  • docs/oauth-flow-testing-guide.md

0. 关键概念(先对齐名词)#

  • Hub:本项目,统一登录中心 + 订阅/权益中心。
  • SaaS App:接入方产品(你的业务站点),作为 OAuth Client。
  • Application(Hub 业务):Hub 内部“应用注册”概念(表 apps),用于权限/来源/计费等归属。
  • OAuth Client(OAuth 协议):OAuth 2.1 客户端(表 oauth_application,Better-Auth OIDC Provider 的事实源)。
  • 授权端点/api/auth/oauth2/authorize
  • Token 端点/api/auth/oauth2/token
  • Consent 页面/auth/consent(展示客户端信息、scope 等)
  • Login 页面/auth/login

1. 本地启动(最小闭环)#

1.1 安装依赖#

npm i

1.2 运行本地数据库迁移(D1 local)#

wrangler d1 migrations apply windchat-members-db --local

1.3 启动 Hub + 两个 demo SaaS(推荐)#

./scripts/start-oauth-demo.sh --migrate

启动后:

  • Hub:http://localhost:3000
  • demo SaaS BFF:http://localhost:4001
  • demo SaaS SPA:http://localhost:4002

2. 创建 Application(Hub 业务应用)#

这一步的目的是:在 Hub 的 apps 表里注册一个 SaaS 应用(用于归属、审计、计费等)。

2.1 方式 A:直接写入 D1(本地)#

wrangler d1 execute windchat-members-db --local --command "
INSERT INTO apps (id, name, status, created_at)
VALUES ('my_saas_app', 'My SaaS App', 'active', unixepoch() * 1000);
"

2.2 方式 B:使用 Admin API(需要管理员会话)#

项目内提供了 /api/admin/apps(POST/PUT/DELETE),但需要满足 requireApiAdmin

  • 先用管理员账号登录(生成 better-auth.session_token cookie)
  • 再携带该 cookie 调用接口

可参考 src/lib/api-guards.tssrc/routes/api/admin/apps.ts 的实现。


3. 创建 OAuth Client(Better-Auth:oauth_application)#

Better-Auth 的 OIDC Provider 会从数据库表 oauth_application 读取客户端信息(见 src/lib/auth.ts 注释)。

3.1 写入一条 OAuth Client 记录(本地)#

下面示例创建一个 public client(推荐给 SPA),并把 app_id 写进 metadata 方便归属:

wrangler d1 execute windchat-members-db --local --command "
INSERT INTO oauth_application (
  id, client_id, client_secret, type, name, icon, metadata,
  disabled, redirect_urls, user_id, created_at, updated_at
) VALUES (
  'my_client_id',
  'my_client_id',
  NULL,
  'public',
  'My SaaS OAuth Client',
  NULL,
  '{\\\"app_id\\\":\\\"my_saas_app\\\"}',
  0,
  '[\\\"http://localhost:4002/callback.html\\\"]',
  NULL,
  unixepoch() * 1000,
  unixepoch() * 1000
);
"

字段说明(关键的几项):

  • client_id:OAuth 的 client_id(唯一)
  • typepublic / confidential
  • redirect_urlsJSON 数组字符串(回调白名单)
  • disabled:禁用开关(1=禁用)

你也可以直接复用项目提供的测试数据:

wrangler d1 execute windchat-members-db --local --file scripts/seed-oauth-test-data.sql

4. 走通 OAuth 2.1(Authorization Code + PKCE)#

4.1 生成 PKCE(SaaS 侧)#

SaaS 需要生成:

  • code_verifier:随机字符串(43~128 chars)
  • code_challengeBASE64URL(SHA256(code_verifier))
  • code_challenge_method=S256

4.2 拼出 authorize URL(跳转到 Hub)#

示例(把参数替换成你的值):

http://localhost:3000/api/auth/oauth2/authorize
  ?response_type=code
  &client_id=my_client_id
  &redirect_uri=http%3A%2F%2Flocalhost%3A4002%2Fcallback.html
  &scope=openid%20profile%20email
  &state=YOUR_STATE
  &code_challenge=YOUR_CODE_CHALLENGE
  &code_challenge_method=S256

浏览器访问后流程为:

  1. 未登录 → 跳转 /auth/login
  2. 登录成功 → 进入 /auth/consent
  3. 同意授权 → 重定向回 redirect_uri?code=...&state=...

4.3 用 code 换 token(SaaS 后端/BFF 执行)#

向 token 端点发起请求:

curl -X POST http://localhost:3000/api/auth/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "client_id=my_client_id" \
  --data-urlencode "redirect_uri=http://localhost:4002/callback.html" \
  --data-urlencode "code=YOUR_CODE_FROM_CALLBACK" \
  --data-urlencode "code_verifier=YOUR_CODE_VERIFIER"

拿到 access_token(以及可能的 refresh_token)后,SaaS 在自己的 BFF 内保存并用于调用 Hub 的受保护 API(或换取业务 token,按你的接入模式设计)。


5. 常见问题(排障清单)#

  1. redirect_uri 不匹配:确认 oauth_application.redirect_urls 中包含完全一致的回调地址(协议/host/path 都要一致)。
  2. PKCE 错误code_challenge 必须是 S256code_verifier 要与生成 challenge 的那一条一致。
  3. CORS 问题/api/auth/oauth2/* 的 CORS 白名单由 OAUTH_DEMO_TRUSTED_ORIGINS 控制(见 src/routes/api/auth/$.ts)。
  4. 客户端信息拉不到(consent 页展示):/api/oauth/client/:clientId 需要用户已登录,且客户端存在于 oauth_application 表。

6. 下一步(生产接入建议)#

  • 只开放 authorization_code + PKCE,SPA 不要持有长期 token;推荐 BFF 模式。
  • 把 SaaS 的 app_id 与 OAuth client 的 client_id 做一对一映射(可写入 oauth_application.metadata 或后续专门的映射表)。
  • 对接完成后,把本地 demo origin(4001/4002)替换为实际域名并加入 trusted origins。