<!-- title: Claude Code认证授权实战：JWT到RBAC全链路 -->

## 概述

本教程面向有一定Web开发基础、希望用Claude Code高效构建生产级认证授权系统的开发者。你将学到JWT双Token机制、OAuth2.0/PKCE流程、Refresh Token轮换与窃取检测、RBAC/ABAC权限模型设计，以及NextAuth.js/Clerk/Supabase Auth三大方案的选型策略。教程以一个完整的多租户SaaS认证系统为实战案例，贯穿所有核心知识点。

**适用人群**：中级前端/全栈开发者，已有Node.js或Next.js项目经验，希望系统掌握Claude Code在安全敏感场景下的开发方法。

## 前置条件

| 要求 | 说明 |
|------|------|
| Claude Code | 已安装并完成基础配置（`claude --version >= 1.0`） |
| Node.js | v20+，推荐使用LTS版本 |
| 包管理器 | pnpm或npm |
| 基础知识 | HTTP协议、REST API、JavaScript/TypeScript |
| 数据库 | PostgreSQL或SQLite（开发环境） |

## 第一部分：认证架构设计

### 1.1 认证 vs 授权：Claude Code如何帮你区分

认证（Authentication）回答"你是谁"，授权（Authorization）回答"你能做什么"。这是两个独立的关注点，但在实践中经常被混淆。Claude Code可以帮助你在项目初期就建立清晰的安全边界。

在CLAUDE.md中明确安全约束是第一步。创建项目根目录的`.claude/CLAUDE.md`或项目级`CLAUDE.md`：

```markdown
# 项目安全约束

## 认证规则
- JWT必须使用RS256或ES256非对称算法，禁止使用HS256
- Access Token有效期不超过15分钟
- Refresh Token必须使用HttpOnly + Secure + SameSite=Strict的Cookie
- 禁止在localStorage中存储任何Token
- 所有密码必须使用bcrypt或argon2哈希，cost factor >= 12

## 授权规则
- 采用RBAC模型，角色定义在数据库而非代码中
- 中间件必须验证每个受保护路由的权限
- API返回403而非404，当资源存在但无权访问时

## 禁止事项
- 不要在任何响应中返回密码哈希
- 不要使用console.log输出Token
- 不要硬编码密钥或密钥
```

这些约束会持续指导Claude Code在后续开发中做出安全选择。

### 1.2 架构选型决策树

在开始编码前，用Claude Code帮你评估适合项目的认证架构。以下是选型决策流程：

```mermaid
flowchart TD
    A[项目认证需求分析] --> B{用户规模?}
    B -->|< 10K用户| C{需要社交登录?}
    B -->|> 10K用户| D{企业级需求?}
    C -->|是| E[OAuth2.0 + PKCE]
    C -->|否| F[Session-based Auth]
    D -->|是| G[SSO/SAML + RBAC]
    D -->|否| H{实时性要求?}
    H -->|高| I[JWT + Refresh Token]
    H -->|低| J[Session + Redis Store]
    E --> K{自建 vs 托管?}
    F --> K
    G --> L[Clerk/Auth0]
    I --> K
    J --> K
    K -->|自建| M[NextAuth.js / 自定义]
    K -->|托管| N[Supabase Auth / Clerk]
```

向Claude Code提供以下Prompt进行选型分析：

```
我正在构建一个[描述你的项目]。项目技术栈是[Next.js/Express/etc]，
预计用户量[规模]，需要[列出：社交登录/企业SSO/多租户/API认证]。
请帮我分析最适合的认证架构方案，给出推荐和理由。
```

## 第二部分：JWT双Token机制实战

### 2.1 项目初始化

```bash
mkdir auth-demo && cd auth-demo
pnpm init
pnpm add express jsonwebtoken bcryptjs cookie-parser helmet cors
pnpm add -D typescript @types/express @types/jsonwebtoken @types/bcryptjs @types/cookie-parser tsx
```

然后用Claude Code生成项目骨架：

```
请帮我搭建一个Express + TypeScript的认证服务项目骨架。要求：
1. src/目录结构：config/、middleware/、routes/、services/、types/
2. 环境变量配置使用dotenv
3. 全局错误处理中间件
4. Helmet安全头配置
5. CORS白名单配置
```

### 2.2 Token生成与验证服务

这是认证系统的核心。Claude Code在无约束条件下常见的错误包括：使用HS256对称算法、Token永不过期、密钥硬编码等。通过CLAUDE.md约束和精确的Prompt可以避免这些问题。

向Claude Code发出以下指令：

```
请实现 src/services/token.service.ts，要求：
1. generateTokenPair(userId: string, role: string) 返回 { accessToken, refreshToken }
2. Access Token: RS256算法，有效期15分钟，payload包含userId、role、iat、exp
3. Refresh Token: 随机生成加密字符串（用crypto.randomBytes），存入数据库
4. verifyAccessToken(token: string) 验证并返回解码payload
5. rotateRefreshToken(oldToken: string) 实现 Refresh Token轮换：
   - 验证旧Token有效性
   - 废弃旧Token
   - 生成新Token对
   - 如果旧Token已被使用过（重放攻击），废弃该用户所有Refresh Token
```

生成的核心逻辑应包含Refresh Token窃取检测机制：

```typescript
// src/services/token.service.ts 核心片段
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
import { db } from '../config/database';

interface TokenPair {
  accessToken: string;
  refreshToken: string;
}

interface TokenPayload {
  userId: string;
  role: string;
  iat: number;
  exp: number;
}

export class TokenService {
  // 从环境变量加载RSA密钥对
  private static get privateKey() {
    return Buffer.from(process.env.JWT_PRIVATE_KEY_B64!, 'base64');
  }

  private static get publicKey() {
    return Buffer.from(process.env.JWT_PUBLIC_KEY_B64!, 'base64');
  }

  static async generateTokenPair(
    userId: string,
    role: string
  ): Promise<TokenPair> {
    // Access Token - 短期
    const accessToken = jwt.sign(
      { userId, role },
      this.privateKey,
      {
        algorithm: 'RS256',
        expiresIn: '15m',
        issuer: 'auth-demo',
        audience: 'auth-demo-api',
        jwtid: crypto.randomUUID(),
      }
    );

    // Refresh Token - 长期但一次性
    const refreshToken = crypto.randomBytes(64).toString('hex');
    const hashedToken = await hashToken(refreshToken);

    await db.query(
      `INSERT INTO refresh_tokens (user_id, token_hash, expires_at)
       VALUES ($1, $2, NOW() + INTERVAL '7 days')`,
      [userId, hashedToken]
    );

    return { accessToken, refreshToken };
  }

  static async rotateRefreshToken(
    oldRefreshToken: string
  ): Promise<TokenPair | null> {
    // 查找Token记录
    const result = await db.query(
      `SELECT id, user_id, token_hash, is_revoked, expires_at
       FROM refresh_tokens
       WHERE expires_at > NOW()
       ORDER BY created_at DESC
       LIMIT 10`,
      []  // 先获取所有活跃Token进行匹配
    );

    let matchedRow = null;
    for (const row of result.rows) {
      const isValid = await verifyTokenHash(oldRefreshToken, row.token_hash);
      if (isValid) {
        matchedRow = row;
        break;
      }
    }

    if (!matchedRow) return null;

    // 窃取检测：如果Token已被使用，说明遭到重放攻击
    if (matchedRow.is_revoked) {
      // 废弃该用户的所有Refresh Token - 安全优先
      await db.query(
        `UPDATE refresh_tokens SET is_revoked = true
         WHERE user_id = $1`,
        [matchedRow.user_id]
      );
      console.error(
        `SECURITY: Refresh token reuse detected for user ${matchedRow.user_id}. All sessions revoked.`
      );
      return null;
    }

    // 正常轮换：废弃旧Token
    await db.query(
      `UPDATE refresh_tokens SET is_revoked = true WHERE id = $1`,
      [matchedRow.id]
    );

    // 获取用户角色用于生成新Access Token
    const user = await db.query(
      `SELECT role FROM users WHERE id = $1`,
      [matchedRow.user_id]
    );

    // 生成新Token对
    return this.generateTokenPair(matchedRow.user_id, user.rows[0].role);
  }
}
```

### 2.3 中间件：Token验证与权限检查

让Claude Code生成认证和授权中间件：

```
请实现两个中间件：

1. src/middleware/auth.middleware.ts
   - authenticate: 从Cookie中提取Access Token（而非Authorization头），
     验证签名和过期时间，将解码后的userId和role附加到req对象
   - 如果Token过期，返回特定错误码 TOKEN_EXPIRED（区别于无效Token）

2. src/middleware/authorize.middleware.ts
   - requireRole(...roles: string[]): 返回中间件，检查req.user.role是否在允许列表中
   - requirePermission(permission: string): 基于RBAC检查用户是否有特定权限
```

关键实现要点——Claude Code需要明确的Token提取策略约束：

```typescript
// src/middleware/auth.middleware.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

export interface AuthRequest extends Request {
  user?: {
    userId: string;
    role: string;
  };
}

export function authenticate(req: AuthRequest, res: Response, next: NextFunction) {
  // 优先从Cookie获取，而非Authorization头
  const token = req.cookies?.accessToken;

  if (!token) {
    return res.status(401).json({
      error: 'UNAUTHORIZED',
      message: 'No authentication token provided',
    });
  }

  try {
    const publicKey = Buffer.from(process.env.JWT_PUBLIC_KEY_B64!, 'base64');
    const decoded = jwt.verify(token, publicKey, {
      algorithms: ['RS256'],
      issuer: 'auth-demo',
      audience: 'auth-demo-api',
    }) as { userId: string; role: string; exp: number };

    req.user = { userId: decoded.userId, role: decoded.role };
    next();
  } catch (err: any) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({
        error: 'TOKEN_EXPIRED',
        message: 'Access token has expired',
      });
    }
    return res.status(401).json({
      error: 'INVALID_TOKEN',
      message: 'Invalid authentication token',
    });
  }
}
```

### 2.4 常见陷阱与修复

Claude Code在认证开发中最容易犯的五个错误及修复方案：

| 陷阱 | Claude Code的默认倾向 | 正确做法 |
|------|----------------------|----------|
| Token存储位置 | 存入localStorage（方便前端读取） | HttpOnly + Secure Cookie |
| 密码处理 | 明文比对或使用MD5 | bcrypt/argon2，cost >= 12 |
| CORS配置 | `Access-Control-Allow-Origin: *` | 白名单机制，限制凭据传递 |
| 错误信息 | "User not found" vs "Wrong password" | 统一返回"Invalid credentials"，防止用户名枚举 |
| Token算法 | 默认HS256（对称加密） | RS256/ES256（非对称加密） |

在CLAUDE.md中添加以下约束以避免这些问题：

```markdown
## 安全审计清单
每次修改认证相关代码后，必须自检：
- [ ] 没有Token出现在浏览器localStorage
- [ ] 没有明文密码出现在日志或响应中
- [ ] CORS配置不包含通配符
- [ ] 登录失败响应不区分"用户不存在"和"密码错误"
- [ ] JWT使用非对称算法
```

## 第三部分：OAuth2.0 / PKCE流程实战

### 3.1 OAuth2.0授权码流程

当需要集成Google、GitHub等第三方登录时，OAuth2.0是标准方案。对于没有后端的SPA或移动应用，PKCE（Proof Key for Code Exchange）扩展是必须的。

```mermaid
sequenceDiagram
    participant U as 用户浏览器
    participant FE as 前端应用
    participant AS as 认证服务器
    participant RS as 资源服务器

    U->>FE: 点击"Google登录"
    FE->>FE: 生成code_verifier + code_challenge
    FE->>AS: 重定向到授权端点<br/>+ code_challenge + redirect_uri
    AS->>U: 显示Google登录页面
    U->>AS: 输入凭据并授权
    AS->>FE: 回调redirect_uri<br/>+ authorization_code
    FE->>AS: 用code + code_verifier换取Token
    AS->>AS: 验证code_challenge = hash(code_verifier)
    AS->>FE: 返回access_token + refresh_token
    FE->>RS: 携带access_token请求资源
    RS->>FE: 返回受保护资源
```

### 3.2 用Claude Code实现Google OAuth

向Claude Code提供精确的实现指令：

```
请实现 src/routes/oauth.routes.ts 和 src/services/oauth.service.ts，
支持Google OAuth2.0登录：

要求：
1. GET /auth/google - 生成授权URL，使用PKCE（code_verifier/challenge），
   将code_verifier暂存到加密的Cookie中
2. GET /auth/google/callback - 处理回调：
   - 验证state参数防CSRF
   - 用code + code_verifier换取Google Token
   - 获取Google用户信息
   - 如果用户已存在，创建新Session
   - 如果用户不存在，创建新用户（从Google信息填充），然后创建Session
3. POST /auth/logout - 清除Cookie，废弃Refresh Token
4. 所有Token通过HttpOnly Secure Cookie传递
```

PKCE核心实现：

```typescript
// src/services/oauth.service.ts
import crypto from 'crypto';

export class OAuthService {
  // PKCE: 生成code_verifier和code_challenge
  static generatePKCE() {
    const codeVerifier = crypto.randomBytes(32).toString('base64url');
    const codeChallenge = crypto
      .createHash('sha256')
      .update(codeVerifier)
      .digest('base64url');
    return { codeVerifier, codeChallenge };
  }

  // 生成防CSRF的state参数
  static generateState() {
    return crypto.randomBytes(32).toString('hex');
  }

  // 构建Google授权URL
  static getGoogleAuthUrl(
    codeChallenge: string,
    state: string,
    redirectUri: string
  ): string {
    const params = new URLSearchParams({
      client_id: process.env.GOOGLE_CLIENT_ID!,
      redirect_uri: redirectUri,
      response_type: 'code',
      scope: 'openid email profile',
      code_challenge: codeChallenge,
      code_challenge_method: 'S256',
      state,
      access_type: 'offline',
      prompt: 'consent',
    });
    return `https://accounts.google.com/o/oauth2/v2/auth?${params}`;
  }

  // 用授权码换取Token
  static async exchangeCodeForTokens(
    code: string,
    codeVerifier: string,
    redirectUri: string
  ) {
    const response = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        code,
        client_id: process.env.GOOGLE_CLIENT_ID!,
        client_secret: process.env.GOOGLE_CLIENT_SECRET!,
        redirect_uri: redirectUri,
        grant_type: 'authorization_code',
        code_verifier: codeVerifier,
      }),
    });

    if (!response.ok) {
      throw new Error('Failed to exchange code for tokens');
    }

    return response.json();
  }
}
```

### 3.3 常见问题排查

**Q: Claude Code生成的OAuth回调为什么缺少state验证？**

这是最常见的遗漏。Claude Code默认倾向于生成"功能可用"的代码，但state参数的CSRF防护经常被忽略。解决方案是在CLAUDE.md中明确要求：

```markdown
## OAuth安全要求
- 所有OAuth流程必须使用state参数防CSRF
- state必须存储在服务端Session或加密Cookie中
- 回调时必须验证state完全匹配
- PKCE的code_verifier不得出现在URL参数中
```

**Q: Token刷新时出现401循环怎么处理？**

这通常是因为前端并发请求都触发了Token刷新。Claude Code生成的代码需要包含请求队列机制：

```typescript
// 前端Token刷新防竞态
let isRefreshing = false;
let refreshSubscribers: Array<(token: string) => void> = [];

function onTokenRefreshed(newToken: string) {
  refreshSubscribers.forEach((cb) => cb(newToken));
  refreshSubscribers = [];
}

async function handleTokenRefresh(failedRequest: any) {
  if (isRefreshing) {
    return new Promise((resolve) => {
      refreshSubscribers.push((token: string) => {
        failedRequest.headers.set('Cookie', `accessToken=${token}`);
        resolve(axios(failedRequest));
      });
    });
  }

  isRefreshing = true;
  try {
    const response = await fetch('/api/auth/refresh', {
      method: 'POST',
      credentials: 'include',
    });
    const newToken = response.headers.get('Set-Cookie');
    onTokenRefreshed(newToken!);
    return axios(failedRequest);
  } finally {
    isRefreshing = false;
  }
}
```

## 第四部分：RBAC权限模型设计

### 4.1 数据库Schema设计

RBAC（Role-Based Access Control）是生产级应用最常用的授权模型。让Claude Code帮你设计数据库Schema：

```
请设计一个支持RBAC的数据库Schema，要求：
1. users表：id, email, password_hash, status, created_at
2. roles表：id, name, description
3. permissions表：id, resource, action, description
4. role_permissions表：多对多关联
5. user_roles表：多对多关联
6. 支持未来扩展到ABAC（基于属性的访问控制）

请生成完整的PostgreSQL DDL，包含索引和约束。
```

生成的Schema：

```sql
-- 用户表
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  email VARCHAR(255) UNIQUE NOT NULL,
  password_hash VARCHAR(255),
  status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'deleted')),
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- 角色表
CREATE TABLE roles (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  name VARCHAR(50) UNIQUE NOT NULL,
  description TEXT,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- 权限表
CREATE TABLE permissions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  resource VARCHAR(100) NOT NULL,  -- 如 'posts', 'users', 'settings'
  action VARCHAR(50) NOT NULL,     -- 如 'read', 'write', 'delete', 'admin'
  description TEXT,
  UNIQUE(resource, action)
);

-- 角色-权限关联
CREATE TABLE role_permissions (
  role_id UUID REFERENCES roles(id) ON DELETE CASCADE,
  permission_id UUID REFERENCES permissions(id) ON DELETE CASCADE,
  PRIMARY KEY (role_id, permission_id)
);

-- 用户-角色关联
CREATE TABLE user_roles (
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  role_id UUID REFERENCES roles(id) ON DELETE CASCADE,
  assigned_at TIMESTAMPTZ DEFAULT NOW(),
  assigned_by UUID REFERENCES users(id),
  PRIMARY KEY (user_id, role_id)
);

-- 性能索引
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_user_roles_user ON user_roles(user_id);
CREATE INDEX idx_role_permissions_role ON role_permissions(role_id);
CREATE INDEX idx_permissions_resource_action ON permissions(resource, action);
```

### 4.2 权限检查中间件

```typescript
// src/middleware/authorize.middleware.ts
import { AuthRequest } from './auth.middleware';
import { Response, NextFunction } from 'express';
import { db } from '../config/database';

// 基于角色的检查
export function requireRole(...allowedRoles: string[]) {
  return (req: AuthRequest, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: 'UNAUTHORIZED' });
    }

    // 从数据库或缓存获取用户角色
    const hasRole = allowedRoles.includes(req.user.role);
    if (!hasRole) {
      return res.status(403).json({ error: 'FORBIDDEN', message: 'Insufficient role' });
    }
    next();
  };
}

// 基于权限的检查（更细粒度）
export function requirePermission(resource: string, action: string) {
  return async (req: AuthRequest, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: 'UNAUTHORIZED' });
    }

    const result = await db.query(
      `SELECT 1 FROM user_roles ur
       JOIN role_permissions rp ON ur.role_id = rp.role_id
       JOIN permissions p ON rp.permission_id = p.id
       WHERE ur.user_id = $1
         AND p.resource = $2
         AND p.action = $3`,
      [req.user.userId, resource, action]
    );

    if (result.rows.length === 0) {
      return res.status(403).json({ error: 'FORBIDDEN', message: 'Insufficient permission' });
    }
    next();
  };
}
```

### 4.3 RBAC vs ABAC决策

```mermaid
flowchart LR
    subgraph RBAC
        R1[用户] -->|拥有角色| R2[管理员]
        R2 -->|角色拥有| R3[权限: users:delete]
    end

    subgraph ABAC
        A1[用户] -->|属性| A2[部门=工程<br/>级别=L5]
        A2 -->|策略| A3[资源.所有者<br/>IN 用户.下属列表]
        A3 -->|环境| A4[时间=工作日<br/>IP=内网]
    end

    RBAC -->|简单场景| S1[用户管理<br/>内容审核]
    ABAC -->|复杂场景| S2[多租户隔离<br/>动态权限<br/>合规审计]
```

向Claude Code的选型Prompt：

```
请根据以下需求帮我决定使用RBAC还是ABAC：
- 1000+用户
- 3个业务部门
- 需要数据行级权限（用户只能看到自己部门的数据）
- 需要临时权限提升（审批流程）

推荐方案，并说明理由。如果推荐ABAC，给出从RBAC演进的路径。
```

## 第五部分：Auth方案对比与选型

### 5.1 三大方案对比

| 维度 | NextAuth.js | Clerk | Supabase Auth |
|------|-------------|-------|---------------|
| **部署模式** | 自托管 | SaaS | SaaS + 自托管 |
| **定价** | 免费开源 | 免费层10K MAU，之后$25/月 | 免费层50K MAU |
| **数据库要求** | 自备 | 内置 | Supabase生态 |
| **社交登录** | 50+ Provider | 20+ Provider | 15+ Provider |
| **自定义程度** | 完全 | 中等 | 高 |
| **企业SSO** | 需Enterprise插件 | 内置SAML/OIDC | 内置SAML |
| **学习曲线** | 中等 | 低 | 中等 |
| **Claude Code适配** | 高（代码级控制） | 中（配置为主） | 高（SQL级控制） |

### 5.2 场景推荐

```mermaid
flowchart TD
    A[选择认证方案] --> B{项目类型?}
    B -->|SaaS产品| C{需要企业客户?}
    B -->|内部工具| D{技术栈?}
    B -->|开源项目| E[NextAuth.js]
    C -->|是| F[Clerk - 内置企业SSO]
    C -->|否| G{预算?}
    D -->|Next.js| E
    D -->|其他| H[Supabase Auth]
    G -->|有限| I[Supabase Auth - 免费层慷慨]
    G -->|充足| F
```

### 5.3 用Claude Code集成NextAuth.js

在Next.js项目中，向Claude Code发出以下指令：

```
请为我的Next.js App Router项目集成NextAuth.js v5（Auth.js），要求：
1. 使用Auth.js v5的authorize模式
2. Credentials Provider：邮箱+密码登录
3. Google Provider：OAuth登录
4. JWT Session策略（不使用数据库Session）
5. 回调中实现：jwt回调中注入role信息，session回调中暴露给客户端
6. 受保护路由通过middleware.ts实现
7. 登录/注册页面使用Server Actions

项目结构：
- app/auth/login/page.tsx - 登录页
- app/auth/register/page.tsx - 注册页
- app/api/auth/[...nextauth]/route.ts - Auth API
- auth.ts - Auth配置
- middleware.ts - 路由保护
```

Auth.js v5核心配置：

```typescript
// auth.ts
import NextAuth from 'next-auth';
import Google from 'next-auth/providers/google';
import Credentials from 'next-auth/providers/credentials';
import bcrypt from 'bcryptjs';
import { db } from '@/lib/db';

export const { handlers, auth, signIn, signOut } = NextAuth({
  providers: [
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    Credentials({
      name: 'credentials',
      credentials: {
        email: { label: 'Email', type: 'email' },
        password: { label: 'Password', type: 'password' },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) {
          return null;
        }

        const user = await db.user.findUnique({
          where: { email: credentials.email as string },
          include: { roles: { include: { role: true } } },
        });

        if (!user || !user.passwordHash) return null;

        const isValid = await bcrypt.compare(
          credentials.password as string,
          user.passwordHash
        );

        if (!isValid) return null;

        return {
          id: user.id,
          email: user.email,
          role: user.roles[0]?.role.name || 'user',
        };
      },
    }),
  ],
  callbacks: {
    async jwt({ token, user }) {
      // 首次登录时注入role
      if (user) {
        token.role = (user as any).role;
        token.userId = user.id;
      }
      return token;
    },
    async session({ session, token }) {
      // 将JWT中的role暴露给客户端
      if (session.user) {
        (session.user as any).role = token.role;
        (session.user as any).id = token.userId;
      }
      return session;
    },
  },
  pages: {
    signIn: '/auth/login',
    error: '/auth/error',
  },
  session: {
    strategy: 'jwt',
    maxAge: 24 * 60 * 60, // 24小时
  },
});
```

## 第六部分：完整实战案例——多租户SaaS认证系统

### 6.1 项目概览

构建一个支持多租户的项目管理SaaS认证系统，包含：
- 邮箱密码注册/登录
- Google OAuth
- 租户隔离
- RBAC权限（Owner/Admin/Member/Viewer）
- API Key认证（供第三方调用）
- 审计日志

```mermaid
flowchart TB
    subgraph 客户端
        UI[Web前端]
        API_CLIENT[API客户端]
    end

    subgraph 认证层
        AUTH[Auth Router<br/>/api/auth/*]
        MW[Auth Middleware]
        PERM[Permission Guard]
    end

    subgraph 业务层
        TENANT[Tenant Service]
        PROJECT[Project Service]
        AUDIT[Audit Service]
    end

    subgraph 数据层
        DB[(PostgreSQL)]
        REDIS[(Redis<br/>Token黑名单)]
    end

    UI -->|Cookie/Token| AUTH
    API_CLIENT -->|API Key| AUTH
    AUTH --> MW
    MW --> PERM
    PERM --> TENANT
    PERM --> PROJECT
    TENANT --> DB
    PROJECT --> DB
    MW --> REDIS
    AUDIT --> DB
```

### 6.2 多租户隔离中间件

```typescript
// src/middleware/tenant.middleware.ts
import { AuthRequest } from './auth.middleware';
import { Response, NextFunction } from 'express';
import { db } from '../config/database';

export async function resolveTenant(
  req: AuthRequest,
  res: Response,
  next: NextFunction
) {
  const tenantSlug = req.params.tenantSlug || req.headers['x-tenant'];

  if (!tenantSlug) {
    return res.status(400).json({ error: 'TENANT_REQUIRED' });
  }

  // 查询租户并验证用户属于该租户
  const result = await db.query(
    `SELECT t.id, t.name, ut.role
     FROM tenants t
     JOIN user_tenants ut ON t.id = ut.tenant_id
     WHERE t.slug = $1 AND ut.user_id = $2 AND t.status = 'active'`,
    [tenantSlug, req.user!.userId]
  );

  if (result.rows.length === 0) {
    return res.status(403).json({ error: 'TENANT_ACCESS_DENIED' });
  }

  // 注入租户上下文
  req.tenant = {
    id: result.rows[0].id,
    name: result.rows[0].name,
    userRole: result.rows[0].role,
  };

  next();
}
```

### 6.3 用Claude Code构建完整系统

将以下Prompt发送给Claude Code，它会按步骤生成完整的认证系统：

```
请帮我构建一个完整的多租户认证系统。我已有以下文件：
- src/services/token.service.ts (Token生成/验证)
- src/middleware/auth.middleware.ts (认证中间件)
- src/middleware/authorize.middleware.ts (授权中间件)
- src/middleware/tenant.middleware.ts (租户隔离)

现在需要你实现：

1. src/routes/auth.routes.ts
   - POST /register - 注册（邮箱验证）
   - POST /login - 登录（限流：5次/分钟/IP）
   - POST /refresh - Token刷新
   - POST /logout - 登出
   - GET /me - 获取当前用户信息

2. src/routes/oauth.routes.ts
   - GET /auth/google - Google OAuth发起
   - GET /auth/google/callback - Google OAuth回调

3. src/routes/tenant.routes.ts
   - POST /tenants - 创建租户
   - GET /tenants/:slug/members - 获取成员列表
   - POST /tenants/:slug/invite - 邀请成员
   - PUT /tenants/:slug/members/:userId/role - 修改成员角色

4. src/services/audit.service.ts
   - 记录所有认证和权限变更操作
   - 支持按用户、租户、操作类型查询

请确保：
- 所有路由都有适当的认证和授权中间件
- 密码使用argon2哈希
- 登录限流使用Redis计数器
- 所有Token通过HttpOnly Secure Cookie传递
```

### 6.4 测试验证

让Claude Code生成认证测试：

```
请为认证系统生成集成测试，覆盖以下场景：
1. 注册成功 → 登录成功 → 获取用户信息 → 登出
2. 注册成功 → 登录 → Token过期 → 刷新Token → 继续
3. 同一Refresh Token被使用两次 → 所有Session被废弃
4. 无效Token访问受保护路由 → 401
5. 普通用户访问管理员路由 → 403
6. 用户访问非所属租户 → 403
7. Google OAuth回调处理成功

使用vitest + supertest。
```

测试核心用例示例：

```typescript
// tests/auth.integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import { app } from '../src/app';

describe('认证流程集成测试', () => {
  describe('注册 → 登录 → 访问 → 登出', () => {
    it('完整正向流程', async () => {
      // 注册
      const registerRes = await request(app)
        .post('/api/auth/register')
        .send({
          email: 'test@example.com',
          password: 'SecureP@ssw0rd!',
          name: 'Test User',
        });
      expect(registerRes.status).toBe(201);

      // 登录
      const loginRes = await request(app)
        .post('/api/auth/login')
        .send({
          email: 'test@example.com',
          password: 'SecureP@ssw0rd!',
        });
      expect(loginRes.status).toBe(200);
      expect(loginRes.headers['set-cookie']).toBeDefined();

      // 提取Cookie
      const cookies = loginRes.headers['set-cookie'];
      const accessTokenCookie = cookies.find((c: string) =>
        c.startsWith('accessToken=')
      );

      // 获取用户信息
      const meRes = await request(app)
        .get('/api/auth/me')
        .set('Cookie', accessTokenCookie);
      expect(meRes.status).toBe(200);
      expect(meRes.body.email).toBe('test@example.com');
    });

    it('Refresh Token重放检测', async () => {
      // 登录获取Token
      const loginRes = await request(app)
        .post('/api/auth/login')
        .send({ email: 'test@example.com', password: 'SecureP@ssw0rd!' });

      const cookies = loginRes.headers['set-cookie'];
      const refreshTokenCookie = cookies.find((c: string) =>
        c.startsWith('refreshToken=')
      );

      // 第一次刷新 - 应该成功
      const refresh1 = await request(app)
        .post('/api/auth/refresh')
        .set('Cookie', refreshTokenCookie);
      expect(refresh1.status).toBe(200);

      // 用旧Token再次刷新 - 应该触发窃取检测
      const refresh2 = await request(app)
        .post('/api/auth/refresh')
        .set('Cookie', refreshTokenCookie);
      expect(refresh2.status).toBe(401);

      // 新Token也应该失效
      const newCookies = refresh1.headers['set-cookie'];
      const newAccessToken = newCookies.find((c: string) =>
        c.startsWith('accessToken=')
      );
      const meRes = await request(app)
        .get('/api/auth/me')
        .set('Cookie', newAccessToken);
      expect(meRes.status).toBe(401);
    });
  });
});
```

### 6.5 一键启动与验证

```bash
# 安装依赖
pnpm install

# 配置环境变量
cp .env.example .env
# 编辑.env，填入：
# JWT_PRIVATE_KEY_B64=<base64编码的RSA私钥>
# JWT_PUBLIC_KEY_B64=<base64编码的RSA公钥>
# GOOGLE_CLIENT_ID=<Google OAuth Client ID>
# GOOGLE_CLIENT_SECRET=<Google OAuth Client Secret>
# DATABASE_URL=postgresql://...

# 生成RSA密钥对
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
base64 -w0 private.pem > private.b64
base64 -w0 public.pem > public.b64

# 初始化数据库
pnpm db:migrate
pnpm db:seed

# 启动服务
pnpm dev

# 运行测试
pnpm test
```

## 第七部分：生产环境部署检查清单

在部署认证系统前，让Claude Code帮你做最终安全审查：

```
请对 src/ 目录下所有认证相关代码进行安全审计，检查以下项目：
1. Token存储：确认所有Token通过HttpOnly Secure Cookie传递
2. 密码处理：确认使用argon2/bcrypt，无明文日志
3. CORS：确认无通配符，白名单正确
4. 限流：确认登录、注册、Token刷新有速率限制
5. 输入验证：确认所有用户输入经过验证
6. SQL注入：确认使用参数化查询
7. 密钥管理：确认无硬编码密钥，所有密钥从环境变量读取
8. 审计日志：确认敏感操作有日志记录

输出格式：每项PASS或FAIL + 具体文件和行号。
```

```mermaid
flowchart LR
    A[代码完成] --> B[安全审计]
    B --> C{PASS?}
    C -->|全部通过| D[部署到Staging]
    C -->|存在FAIL| E[修复问题]
    E --> B
    D --> F[渗透测试]
    F --> G{通过?}
    G -->|是| H[部署到Production]
    G -->|否| E
    H --> I[监控告警]
    I --> J[定期Token轮换<br/>密钥轮换]
```

## 延伸阅读

- [OWASP Authentication Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html) — 认证安全最佳实践的权威指南
- [Auth.js v5 官方文档](https://authjs.dev/) — NextAuth.js v5的完整参考
- [JWT Best Practices (RFC 8725)](https://datatracker.ietf.org/doc/html/rfc8725) — JWT安全使用的RFC规范
- [OAuth 2.0 for Browser-Based Apps (BCP)](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-browser-based-apps) — 浏览器应用的OAuth最佳实践

## 参考来源

1. [Authentication Patterns with Claude Code: JWT, Sessions, and OAuth Done Right](https://dev.to/myougatheaxo/authentication-patterns-with-claude-code-jwt-sessions-and-oauth-done-right-1jm) — Claude Code认证模式的实践指南，涵盖JWT/Session/OAuth三种模式的安全实现
2. [Claude Code Best Practices: 18 Patterns From Production Teams](https://www.clarista.io/blog/claude-code-best-practices) — 来自生产团队的18个Claude Code最佳实践，包含CLAUDE.md设计和安全约束配置
3. [How to Build an Authentication System with Claude Code](https://claudecode-lab.com/en/blog/claude-code-authentication-implementation/) — 使用Claude Code构建JWT+OAuth认证系统的完整教程
4. [JWT Authentication with Claude Code: Refresh Token Rotation](https://dev.to/myougatheaxo/jwt-authentication-with-claude-code-refresh-token-rotation-and-theft-detection-1okk) — Refresh Token轮换与窃取检测机制的深度实现
5. [Next.js Authentication in 2026: NextAuth vs Clerk vs Supabase](https://samioda.com/en/blog/nextjs-authentication-guide-nextauth-clerk-supabase) — 三大认证方案的2026年最新对比分析

## 训练营适用性评估

| 训练营类型 | 适用性 | 预计时长 | 说明 |
|-----------|--------|---------|------|
| 全栈开发训练营 | ★★★★★ | 4-6小时 | 认证是全栈必学内容，可拆为2-3个Session |
| 安全工程师训练营 | ★★★★☆ | 3-4小时 | 侧重安全审计和攻击防护部分 |
| 企业级应用训练营 | ★★★★★ | 6-8小时 | 多租户+RBAC是企业级核心需求 |
| 前端开发训练营 | ★★★☆☆ | 2-3小时 | 侧重OAuth集成和Token管理，跳过后端实现 |
| 运维/DevOps训练营 | ★★☆☆☆ | 1-2小时 | 侧重部署检查清单和密钥管理 |

**推荐教学路径**：先完成Part 1-3的JWT基础（2小时），然后根据学员方向选择OAuth集成（前端方向）或RBAC设计（后端方向），最后用Part 6的完整案例做综合练习。

> [!tip] 51学通信知识星球
> **星主：爱卫生**（微信：gprshome201101）——公众号「51学通信」创办者，19 年通信行业运维与讲师经验，著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书（各大电商平台有售）。
> **星球定位**：深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域，主打运维岗位与信令分析。
> **当前规模**：5200 篇主题 / 990 个回答，198 元/年。
> **加入福利**：加入星球后请加爱卫生微信，拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题，另有群内分享。
> **AI 加持**：51学通信知识星球已作为 skill 接入 WorkBuddy，使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例，做总结、找文档和 PPT 等场景全覆盖。

![51学通信知识星球海报-扫码加入](https://wiki.500nian.com/images/zhixingqiu_qrcode.png)
