# 独立游戏后台接入 API

## 统一原则

NBTiktok 负责认证和签发 JWT，游戏客户端负责选择业务后台 URL。本地、测试、正式后台使用同一个 Backend JWT 合同、Audience 和 JWKS，不存在额外开发密钥。HTTP 与第三方后台 WebSocket 共用 `credentials.gameBackend.token`。

客户端只向 NBTiktok 提交邮箱、密码、`gameId`、`mode` 和 `clientBuild`。游戏启用独立后台后，登录与 refresh 返回 `backendAuth` 和 `credentials.gameBackend`，但不返回任何业务 API 地址。

```http
POST /api/game-client/v2/login
Content-Type: application/json

{"email":"player@example.com","password":"...","gameId":"game_one","mode":"debug","clientBuild":120}
```

客户端从可信构建配置或仅限开发构建的本地配置读取 Backend URL。HTTP 发送 JWT 时使用 `Authorization: Bearer <token>`，禁止跨 Origin 自动重定向。建议分别配置 `NBT_GAME_BACKEND_HTTP_URL` 与 `NBT_GAME_BACKEND_WS_URL`。

## Backend JWT

| 项目 | 合同 |
| --- | --- |
| Backend JWT | `credentials.gameBackend` |
| 公钥 | `/.well-known/game-backend-jwks.json` |
| 身份键 | `(gameId, space, sub)` |

- JWKS：`/.well-known/game-backend-jwks.json`
- Audience：`urn:nbtiktok:game-backend:<gameId>`
- Scope：`game.session`
- 最长有效期：30 分钟（1,800 秒）
- 身份键：`(gameId, space, sub)`

后台必须精确校验 EdDSA 签名、`kid`、Issuer、Audience、`gameId`、`scope`、`space`、`account`、`iat/nbf/exp` 和最长生命周期。Sandbox 与 Production 必须采用独立数据库、Schema 或强制分区键。

Node.js 使用 `@nbtiktok/game-backend-auth` 0.4.x 的 `createVerifier`。同一配置可部署到本地、测试和正式 Server。

## 第三方后台 WebSocket

第三方后台 WSS 与平台事件 `/ws/game-client/v2` 完全独立：前者使用 Backend JWT，后者继续使用平台一次性 WebSocket Ticket，不得混用。

客户端以固定子协议 `nbt.game-backend.v1` 连接可信 WSS URL。Token 不放在 URL、Cookie 或子协议中；连接后 5 秒内发送：

```json
{"type":"nbt.auth","token":"<credentials.gameBackend.token>"}
```

后台调用 SDK `verifyWebSocketAuthFrame(text, verifier, currentClaims?)`。首次成功及续签都返回：

```json
{"type":"nbt.auth.ok","expiresAt":"2026-09-02T12:30:00.000Z"}
```

平台 refresh 返回新 Backend JWT 后，客户端在原连接再次发送 `nbt.auth`。SDK 要求新旧 `gameId`、`space`、`sub`、`sid` 一致，只更新账户快照、配置版本与到期时间。认证前禁止业务帧；认证帧只接受文本且最大 20 KiB。未认证或 Token 失效使用关闭码 `4401`，身份变化 `4403`，超时 `4408`，过大 `1009`，JWKS 暂不可用 `1013`。

## 本地调试

本地开发可使用：

```env
NBT_GAME_BACKEND_HTTP_URL=http://127.0.0.1:19101
NBT_GAME_BACKEND_WS_URL=ws://127.0.0.1:19101/ws
```

明文 `http/ws` 仅允许 `localhost`、`127.0.0.0/8` 与 `[::1]`；其他地址必须使用 `https/wss`。浏览器后台还应精确校验 Origin；无 Origin 的原生客户端仍必须完成 JWT 首帧认证。可运行示例位于 `examples/independent-game-backend`，Token 只保存在浏览器或 Node 客户端内存中。

## 浏览器 Portal

第三方后台生成 `state` 和 PKCE S256，随后跳转：

```text
GET /game-portal/authorize?response_type=code&client_id=game_one&redirect_uri=...&state=...&code_challenge=...&code_challenge_method=S256&prompt=login
```

邮箱密码只在 NBTiktok 官方页面输入。成功后回调只携带 60 秒一次性 Code 和原 `state`，服务端通过 `POST /api/game-portal/v2/token` 兑换 Portal JWT。

正式回调必须与管理员登记的 HTTPS `portalRedirectUri` 精确匹配。本地仅允许：

```text
http://127.0.0.1:<port>/__nbt/callback
http://[::1]:<port>/__nbt/callback
```

Portal JWKS 为 `/.well-known/game-portal-jwks.json`。Portal JWT 最长 10 分钟；第三方验签后创建自己的 HttpOnly Cookie，最长 8 小时且不得超过 `authorizationExpiresAt`。NBTiktok 不代理业务请求，也不维护第三方 Cookie。

Node SDK 使用 `createPortalAuthorizationRequest` 生成 state/PKCE，通过 `exchangePortalCode` 兑换，并用 `createPortalVerifier` 验证 Portal JWT。
