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
跑¶
真实代码对照¶
| 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 防截获¶
- 客户端发 authorize 请求时只发 challenge(不泄露 verifier) - 客户端换 token 时才发 verifier - 攻击者截获 code 后没 verifier,换不出 token2️⃣ 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
进阶练习¶
- 加 refresh_token 流程:用 refresh_token 换新 access_token
- 加 scope 细粒度:每个 API 端点声明所需 scope
- 加 JWKS:从服务器拉公钥验签(替代 HS256 共享密钥)
- 加 token revocation:服务器支持撤销 access_token
- 加 PKCE plain method:
code_challenge = verifier(无 SHA256,老 client 兼容) - 加 device authorization flow:无浏览器场景(CLI 工具)
相关阅读¶
- topics/deep-dive-mcp-auth.md —— 2465 行完整分析
- topics/authentication.md —— 鉴权跨阶段专题
- topics/deep-dive-auth.md —— utils/auth.ts 鉴权工具
- OAuth 2.0 RFC 6749
- PKCE RFC 7636
- JWT RFC 7519