mcpField
A
authentication-patterns

authentication-patterns

github.com/rohitg00安全2.8k187 次安装24 周前更新
代码仓库

关于 authentication-patterns

面向实现 Web 身份验证与授权的开发者,涵盖令牌处理、OAuth2 PKCE、RBAC 模型及常见安全风险。

主要功能

  1. 01JWT 访问与刷新令牌模式
  2. 02Bearer 令牌身份验证中间件
  3. 03OAuth2 授权码与 PKCE 流程
  4. 04RBAC 权限模型
  5. 05身份验证安全检查清单

适用场景

  1. 01设计基于令牌的 Web 身份验证
  2. 02为公共客户端加入 OAuth2 PKCE
  3. 03在路由和数据访问层检查权限

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/rohitg00/awesome-claude-code-toolkit --skill authentication-patterns

安装后在 Claude Code 中输入/authentication-patterns确认已加载。

详细介绍

身份验证模式

JWT 访问令牌和刷新令牌

import jwt from "jsonwebtoken";

interface TokenPayload {
  sub: string;
  email: string;
  roles: string[];
}

function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { sub: user.id, email: user.email, roles: user.roles },
    process.env.JWT_SECRET!,
    { expiresIn: "15m", issuer: "auth-service" }
  );

  const refreshToken = jwt.sign(
    { sub: user.id, tokenVersion: user.tokenVersion },
    process.env.REFRESH_SECRET!,
    { expiresIn: "7d", issuer: "auth-service" }
  );

  return { accessToken, refreshToken };
}

function verifyAccessToken(token: string): TokenPayload {
  return jwt.verify(token, process.env.JWT_SECRET!, {
    issuer: "auth-service",
  }) as TokenPayload;
}

使用有效期较短的访问令牌(15 分钟)和有效期较长的刷新令牌(7 天)。将刷新令牌存储在仅限 HTTP 访问的 Cookie 中。

身份验证中间件

function authenticate(req: Request, res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "Missing authorization header" });
  }

  try {
    const payload = verifyAccessToken(header.slice(7));
    req.user = payload;
    next();
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ error: "Token expired" });
    }
    return res.status(401).json({ error: "Invalid token" });
  }
}

function authorize(...roles: string[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) return res.status(401).json({ error: "Not authenticated" });
    if (!roles.some(role => req.user.roles.includes(role))) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }
    next();
  };
}

app.get("/admin/users", authenticate, authorize("admin"), listUsers);

使用 PKCE 的 OAuth2 授权码流程

import crypto from "crypto";

function generatePKCE() {
  const verifier = crypto.randomBytes(32).toString("base64url");
  const challenge = crypto
    .createHash("sha256")
    .update(verifier)
    .digest("base64url");
  return { verifier, challenge };
}

app.get("/auth/login", (req, res) => {
  const { verifier, challenge } = generatePKCE();
  req.session.codeVerifier = verifier;

  const params = new URLSearchParams({
    response_type: "code",
    client_id: process.env.OAUTH_CLIENT_ID!,
    redirect_uri: `${process.env.APP_URL}/auth/callback`,
    scope: "openid profile email",
    code_challenge: challenge,
    code_challenge_method: "S256",
    state: crypto.randomBytes(16).toString("hex"),
  });

  res.redirect(`${process.env.OAUTH_AUTHORIZE_URL}?${params}`);
});

app.get("/auth/callback", async (req, res) => {
  const { code } = req.query;

  const tokenResponse = await fetch(process.env.OAUTH_TOKEN_URL!, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code: code as string,
      redirect_uri: `${process.env.APP_URL}/auth/callback`,
      client_id: process.env.OAUTH_CLIENT_ID!,
      code_verifier: req.session.codeVerifier,
    }),
  });

  const tokens = await tokenResponse.json();
  const userInfo = jwt.decode(tokens.id_token);

  req.session.user = { id: userInfo.sub, email: userInfo.email };
  res.redirect("/dashboard");
});

RBAC 模型

interface Permission {
  resource: string;
  action: "create" | "read" | "update" | "delete";
}

const ROLE_PERMISSIONS: Record<string, Permission[]> = {
  viewer: [
    { resource: "posts", action: "read" },
    { resource: "comments", action: "read" },
  ],
  editor: [
    { resource: "posts", action: "create" },
    { resource: "posts", action: "read" },
    { resource: "posts", action: "update" },
    { resource: "comments", action: "create" },
    { resource: "comments", action: "read" },
  ],
  admin: [
    { resource: "*", action: "create" },
    { resource: "*", action: "read" },
    { resource: "*", action: "update" },
    { resource: "*", action: "delete" },
  ],
};

function hasPermission(roles: string[], resource: string, action: string): boolean {
  return roles.some(role =>
    ROLE_PERMISSIONS[role]?.some(
      p => (p.resource === resource || p.resource === "*") && p.action === action
    )
  );
}

反模式

  • 将 JWT 存储在 localStorage 中(容易受到 XSS 攻击;应使用仅限 HTTP 访问的 Cookie)
  • 在多个服务之间为 JWT 使用对称密钥(应使用基于密钥对的 RS256)
  • 验证令牌时未校验 iss、aud 和 exp 声明
  • 不使用 bcrypt/argon2,而是自行实现密码哈希
  • 基于 Cookie 的身份验证缺少 CSRF 防护
  • 为“未找到用户”和“密码错误”返回不同的错误消息(可能导致用户枚举)

检查清单

  • 访问令牌的有效期较短(不超过 15 分钟)
  • 刷新令牌存储在带有 HTTP-only、Secure、SameSite 属性的 Cookie 中
  • 使用 bcrypt 或 argon2 对密码进行哈希(绝不使用 MD5/SHA)
  • 公共客户端使用 OAuth2 PKCE 流程
  • 在路由层和数据访问层检查 RBAC 权限
  • 通过版本计数器或阻止列表支持令牌撤销
  • 为基于 Cookie 的身份验证启用 CSRF 防护
  • 身份验证错误不会泄露用户是否存在

评论

支持 Markdown