跳转至

Mini OAuth 2.0 + PKCE

从 2465 行的 src/services/mcp/auth.ts 提炼到 ~250 行的 OAuth 2.0 + PKCE + JWT 实现。 4 阶段完整流程 + 3 错误场景(PKCE 失败 / CSRF 攻击 / 过期 token)全测。

文件

mini-mcp-auth/
├── src/
│   ├── oauth.ts  PKCE / state / JWT / mock auth server / client(~220 行)
│   └── cli.ts    4 阶段 + 3 错误场景
└── README.md

npm install && npm start

真实代码对照

Demo 真实文件 简化
generateCodeVerifier mcp/auth.ts:50 generatePKCE (~200 行) 32 bytes random + SHA256
generateCodeChallenge 同上 1 行 SHA256
signJwt / verifyJwt mcp/auth.ts:1500 signJwt (~500 行) HS256 + 7 字段 payload
OAuthClient mcp/auth.ts:300 OAuthClient (~800 行) 4 阶段 + error handling
authorize / token 真实 HTTP endpoints mock 内存函数(无网络)

4 阶段流程图

┌─────────┐                ┌─────────┐                ┌─────────┐
│ Client  │                │   Auth  │                │  Token  │
│         │                │ Server  │                │   EP    │
└────┬────┘                └────┬────┘                └────┬────┘
     │                          │                          │
     │── 1. /authorize ────────►│                          │
     │   (client_id, scope,     │                          │
     │    state, code_challenge)│                          │
     │◄── redirect (code, ──────│                          │
     │     state)               │                          │
     │                          │                          │
     │── 2. /token ────────────────────────────────────────►│
     │   (code, code_verifier)  │                          │
     │◄── access_token ─────────────────────────────────────│
     │     refresh_token        │                          │
     │                          │                          │
     │── 3. /api (Bearer ──────────────────────────────────►│
     │    access_token)         │                          │
     │◄── protected data ────────────────────────────────────│

核心 4 件套

1️⃣ PKCE 防截获

verifier  = 32 random bytes (base64url)
challenge = SHA256(verifier) (base64url)
- 客户端发 authorize 请求时只发 challenge(不泄露 verifier) - 客户端换 token 时才发 verifier - 攻击者截获 code 后没 verifier,换不出 token

2️⃣ state 防 CSRF

  • 客户端生成随机 state,附在 authorize 请求
  • 服务器回调时原样返回
  • 客户端校验 state == 之前存的

3️⃣ JWT = header.payload.signature

  • header: { alg: "HS256", typ: "JWT" }
  • payload: { sub, iss, aud, exp, iat, scope }
  • signature: HMAC-SHA256(header.payload, secret)
  • 验证:HMAC 重算 + timingSafeEqual + 过期检查

4️⃣ Mock auth server = 内存 Map

真实是 HTTP endpoint;本 demo 用 codeStore: Map<code, {challenge, clientId, ...}> 内存存储。5 分钟 setTimeout 清理(unref 不阻塞进程)。

3 错误场景

场景 触发 拦截位置
PKCE 失败 错误 verifier token endpoint 验 SHA256 不匹配
CSRF 攻击 错误 state client.handleCallback 验 state 不匹配
过期 token exp < now verifyJwt 检查 exp

测试输出

📌 Phase 1: Build authorize request
📌 Phase 2: User approves → mock authorization server
📌 Phase 3: Handle callback + exchange code → token
  ✅ state verified (CSRF protected)
📌 Phase 4: Call protected API with Bearer token
  ✅ JWT signature valid + not expired

⚠️  Error Test 1: Wrong code_verifier (PKCE mismatch)
  ✅ BLOCKED: PKCE verification failed: code_verifier doesn't match code_challenge

⚠️  Error Test 2: Wrong state (CSRF attack)
  ✅ BLOCKED: State mismatch: CSRF attack? (got ATTACKER..., expected EI8Kjepn...)

⚠️  Error Test 3: Expired JWT
  ✅ BLOCKED: Invalid or expired access token

进阶练习

  1. 加 refresh_token 流程:用 refresh_token 换新 access_token
  2. 加 scope 细粒度:每个 API 端点声明所需 scope
  3. 加 JWKS:从服务器拉公钥验签(替代 HS256 共享密钥)
  4. 加 token revocation:服务器支持撤销 access_token
  5. 加 PKCE plain methodcode_challenge = verifier(无 SHA256,老 client 兼容)
  6. 加 device authorization flow:无浏览器场景(CLI 工具)

相关阅读