15 KiB
Nanobot Auth Portal 接口文档
1. 文档范围
本文档覆盖 nanobot-auth-portal 当前实际依赖和对接的接口:
- Auth Portal 前端页面路由:
/、/login、/register - 后端认证接口:
/api/auth/* - 登录完成后的浏览器交接接口:目标业务前端
/handoff
说明:
- Auth Portal 自身是一个独立前端,不在本仓库内实现后端 API。
- 文档中的后端接口来自当前联调使用的
nanobot-backend实现。 - 当前项目只直接调用
POST /api/auth/login和POST /api/auth/register,但为了便于联调,本文档一并补全了同一条登录链路上的handoff、me、logout接口。
2. 服务地址与环境变量
2.1 Auth Portal 页面地址
默认运行在当前主机 3081 端口。
2.2 后端 API Base URL
Auth Portal 按以下优先级计算后端地址:
NEXT_PUBLIC_BACKEND_API_URL- 浏览器当前域名 +
NEXT_PUBLIC_BACKEND_API_PORT,默认10000 - SSR 回退地址
http://127.0.0.1:10000
示例:
NEXT_PUBLIC_BACKEND_API_URL=https://nanobot-api.bwgdi.com
NEXT_PUBLIC_BACKEND_API_PORT=10000
NEXT_PUBLIC_FRONTEND_PORT=3080
2.3 目标业务前端 Base URL
登录或注册成功后,Auth Portal 会从响应里解析目标前端地址,优先级如下:
backend_connection.frontend_base_urlbackend_connection.api_base_urlbackend_connection.public_base_urllocal_backend.public_base_url
如果命中的是后 2 到 4 项,Auth Portal 会将端口替换为 NEXT_PUBLIC_FRONTEND_PORT,默认 3080。
3. 公共约定
3.1 请求格式
- 请求体格式:
application/json - 成功响应:JSON
- Auth Portal 前端请求超时:
8000ms
3.2 鉴权方式
POST /api/auth/login:无需鉴权POST /api/auth/register:无需鉴权POST /api/auth/handoff/consume:无需鉴权GET /api/auth/me:需要Authorization: Bearer <access_token>POST /api/auth/logout:可带Authorization: Bearer <access_token>
3.3 错误响应格式
后端主要返回:
{
"detail": "错误信息"
}
Auth Portal 前端收到非 2xx 时,会转成如下错误文案:
接口错误 <status>: <detail>
3.4 Token 与 handoff 约定
access_token是后端进程内维护的 Web 会话 token。refresh_token当前实现始终返回空字符串""。- 当前认证链路没有独立的
/api/auth/refresh。 handoff_code是短时有效的浏览器交接码。handoff_code默认 TTL 为90秒。handoff_code被消费后默认允许15秒内的短暂重放窗口,用于前端页面刷新或重试。
4. 数据模型
4.1 BackendConnectionInfo
{
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"api_base_url": "https://nanobot-api.example.com",
"ws_base_url": "wss://nanobot-api.example.com",
"frontend_base_url": "https://nanobot.example.com",
"registered": true
}
字段说明:
backend_id:backend 唯一标识client_id:backend 在 AuthZ 中的客户端 IDname:backend 名称public_base_url:公开后端地址api_base_url:业务 API 地址ws_base_url:WebSocket 地址frontend_base_url:目标业务前端入口地址registered:当前 backend 是否已完成本地身份注册
4.2 AuthzLocalBackendStatus
{
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"authz": {
"enabled": true,
"base_url": "https://authz.example.com"
}
}
字段说明:
backend_id:本地 backend identity 中记录的 backend IDclient_id:本地 backend identity 中记录的 client IDname:本地 backend 名称public_base_url:本地 backend 对外地址authz.enabled:是否启用 AuthZauthz.base_url:当前 backend 使用的 AuthZ 服务地址
4.3 RegisterAuthzStatus
仅 POST /api/auth/register 响应会返回。
{
"enabled": true,
"base_url": "https://authz.example.com",
"user_registered": true,
"backend_registered": true
}
字段说明:
enabled:本次注册是否启用了 AuthZbase_url:本次注册使用的 AuthZ 服务地址user_registered:用户是否已在 AuthZ 完成注册或确认存在backend_registered:backend 身份是否已在 AuthZ 完成注册
4.4 TokenResponse
登录、注册、handoff 消费都会返回 token 响应,但字段不完全相同。
{
"access_token": "opaque-token",
"refresh_token": "",
"token_type": "bearer",
"user_id": "bwgdi",
"username": "bwgdi",
"email": "steven@example.com",
"role": "owner",
"handoff_code": "short-lived-code",
"handoff_expires_at": 1760000000,
"existing_user": false,
"authz": {
"enabled": true,
"base_url": "https://authz.example.com",
"user_registered": true,
"backend_registered": true
},
"backend_connection": {
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"api_base_url": "https://nanobot-api.example.com",
"ws_base_url": "wss://nanobot-api.example.com",
"frontend_base_url": "https://nanobot.example.com",
"registered": true
},
"local_backend": {
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"authz": {
"enabled": true,
"base_url": "https://authz.example.com"
}
}
}
字段说明:
access_token:后端签发的登录 tokenrefresh_token:当前固定为空字符串token_type:当前固定为beareruser_id:当前等于用户名username:用户名email:仅注册响应会返回,登录响应通常没有role:当前固定为ownerhandoff_code:仅登录/注册响应返回,用于跳转到目标前端handoff_expires_at:UNIX 时间戳,单位秒existing_user:仅注册响应返回,表示这次注册是否命中了已有用户authz:仅注册响应返回,表示本次 AuthZ 注册结果backend_connection:当前登录用户对应的目标 backend 路由信息local_backend:本地 backend identity 视图
4.5 AuthUser
GET /api/auth/me 返回:
{
"id": "bwgdi",
"username": "bwgdi",
"email": "",
"role": "owner",
"quota_tier": "single-user"
}
5. 前端页面路由
5.1 GET /
用途:Portal 首页。
行为:立即重定向到 /login。
5.2 GET /login
用途:显示登录页面。
Query 参数:
next:登录成功后希望进入的目标业务前端路径,默认/
示例:
/login?next=/mcp
成功后浏览器会跳转到:
<frontend_base_url>/handoff?code=<handoff_code>&next=/mcp
5.3 GET /register
用途:显示注册页面。
Query 参数:
next:注册成功后希望进入的目标业务前端路径,默认/mcp
示例:
/register?next=/mcp
6. 后端认证接口
6.1 POST /api/auth/login
用途:用户名密码登录,并返回目标 backend 路由与 handoff 信息。
鉴权:否。
请求体:
{
"username": "bwgdi",
"password": "123456"
}
请求字段:
username:必填,登录用户名password:必填,登录密码
成功响应:200 OK
{
"access_token": "opaque-token",
"refresh_token": "",
"token_type": "bearer",
"user_id": "bwgdi",
"username": "bwgdi",
"role": "owner",
"handoff_code": "short-lived-code",
"handoff_expires_at": 1760000000,
"backend_connection": {
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"api_base_url": "https://nanobot-api.example.com",
"ws_base_url": "wss://nanobot-api.example.com",
"frontend_base_url": "https://nanobot.example.com",
"registered": true
},
"local_backend": {
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"authz": {
"enabled": true,
"base_url": "https://authz.example.com"
}
}
}
错误码:
400 Bad Request:username为空401 Unauthorized:用户名或密码错误500 Internal Server Error:本地用户文件不存在或格式非法
实现备注:
- 登录成功后,Portal 必须使用
handoff_code跳到目标业务前端/handoff。 - 如果后端没有返回
frontend_base_url或handoff_code,Portal 会直接提示错误,不会继续跳转。
6.2 POST /api/auth/register
用途:创建本地登录账号,并在需要时完成 AuthZ 用户/后端注册,然后返回 handoff 信息。
鉴权:否。
当前 Portal 页面实际发送字段:
{
"username": "bwgdi",
"email": "steven@example.com",
"password": "123456"
}
后端支持的完整请求体:
{
"username": "bwgdi",
"email": "steven@example.com",
"password": "123456",
"authz_base_url": "https://authz.example.com",
"backend_name": "Boardware Genius",
"backend_id": "backend-001",
"base_url": "https://nanobot-api.example.com",
"frontend_base_url": "https://nanobot.example.com"
}
请求字段:
username:必填,用户名email:选填,邮箱password:必填,密码authz_base_url:选填,覆盖后端当前配置的 AuthZ 地址backend_name:选填,注册 backend 时使用的名称backend_id:选填,期望写入或复用的 backend IDbase_url:选填,注册 backend 时使用的公开 API 地址frontend_base_url:选填,注册 backend 时使用的公开前端地址
成功响应:当前实现为 200 OK
{
"access_token": "opaque-token",
"refresh_token": "",
"token_type": "bearer",
"user_id": "bwgdi",
"username": "bwgdi",
"email": "steven@example.com",
"role": "owner",
"handoff_code": "short-lived-code",
"handoff_expires_at": 1760000000,
"existing_user": false,
"authz": {
"enabled": true,
"base_url": "https://authz.example.com",
"user_registered": true,
"backend_registered": true
},
"backend_connection": {
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"api_base_url": "https://nanobot-api.example.com",
"ws_base_url": "wss://nanobot-api.example.com",
"frontend_base_url": "https://nanobot.example.com",
"registered": true
},
"local_backend": {
"backend_id": "backend-001",
"client_id": "client-001",
"name": "Boardware Genius",
"public_base_url": "https://nanobot-api.example.com",
"authz": {
"enabled": true,
"base_url": "https://authz.example.com"
}
}
}
错误码:
400 Bad Request:username或password缺失409 Conflict:用户名已存在,且提交的密码与已有密码不一致4xx/5xx:AuthZ 上游服务直接返回的错误502 Bad Gateway:调用 AuthZ 服务失败
实现备注:
- 如果用户名已存在但密码相同,后端会把本次请求视为“继续完成配置”,不会直接报错。
- 如果本地
web_auth_users.json不存在,注册会自动创建。 - 注册成功后同样必须依赖
handoff_code进入目标业务前端。
6.3 POST /api/auth/handoff/consume
用途:由目标业务前端消费 handoff_code,换取可直接使用的 access_token。
鉴权:否。
请求体:
{
"code": "short-lived-code"
}
成功响应:200 OK
{
"access_token": "opaque-token",
"refresh_token": "",
"token_type": "bearer",
"user_id": "bwgdi",
"username": "bwgdi",
"role": "owner"
}
错误码:
400 Bad Request:code为空401 Unauthorized:code无效、已失效,或 payload 非法410 Gone:code已过期,或已超过重放窗口
实现备注:
- 当前目标前端在
/handoff页面里调用该接口。 - 目标前端拿到 token 后会继续调用
GET /api/auth/me校验登录态。
6.4 GET /api/auth/me
用途:获取当前登录用户信息。
鉴权:是,必须提供 Authorization: Bearer <access_token>。
请求头:
Authorization: Bearer opaque-token
成功响应:200 OK
{
"id": "bwgdi",
"username": "bwgdi",
"email": "",
"role": "owner",
"quota_tier": "single-user"
}
错误码:
401 Unauthorized:缺少Authorization头401 Unauthorized:Authorization头格式错误401 Unauthorized:token 为空401 Unauthorized:token 无效或已失效
6.5 POST /api/auth/logout
用途:注销当前 token。
鉴权:可选。带 token 时会尝试从后端内存中删除该 token。
请求头:
Authorization: Bearer opaque-token
成功响应:200 OK
{
"ok": true
}
实现备注:
- 即使没有传 token,接口也会返回
{"ok": true}。 - Portal 或业务前端通常还会同步清理浏览器本地 token。
7. 登录与跳转链路
7.1 登录链路
- 用户访问目标业务前端的受保护页面,例如
/mcp - 业务前端发现未登录,跳转到 Auth Portal:
/login?next=/mcp - Auth Portal 提交
POST /api/auth/login - 后端返回
handoff_code与backend_connection.frontend_base_url - Auth Portal 跳转到目标业务前端:
https://target-frontend.example.com/handoff?code=<handoff_code>&next=/mcp
- 目标业务前端在
/handoff页面调用POST /api/auth/handoff/consume - 目标业务前端保存 token 后调用
GET /api/auth/me - 用户最终进入
/mcp
7.2 注册链路
- 用户访问 Auth Portal:
/register?next=/mcp - Auth Portal 提交
POST /api/auth/register - 后端按需创建本地用户,并与 AuthZ 同步用户/后端身份
- 后端返回
handoff_code与目标前端地址 - Portal 跳转到目标业务前端
/handoff - 后续步骤与登录链路一致
8. 联调示例
8.1 登录
curl -X POST 'https://nanobot-api.example.com/api/auth/login' \
-H 'Content-Type: application/json' \
-d '{
"username": "bwgdi",
"password": "123456"
}'
8.2 消费 handoff_code
curl -X POST 'https://nanobot-api.example.com/api/auth/handoff/consume' \
-H 'Content-Type: application/json' \
-d '{
"code": "short-lived-code"
}'
8.3 获取当前用户
curl 'https://nanobot-api.example.com/api/auth/me' \
-H 'Authorization: Bearer opaque-token'
9. 当前实现限制
refresh_token目前没有实际用途,返回值固定为空字符串。- token 与 handoff code 都保存在后端进程内存中,后端重启后会失效。
- 本仓库前端页面目前没有暴露
register的高级可选字段,只使用基础注册参数。 GET /api/auth/me当前返回的email固定为空字符串,不会回填注册邮箱。