关于 authentication-patterns
authentication-patterns 是为开发者提供的认证实现与安全审查参考技能,涵盖常见认证方案、提供方选择和发布前检查。它汇总令牌、会话、密码及安全控制方面的实践建议。
主要功能
- 01比较会话、JWT、OAuth 和 Passkeys
- 02提供认证方案选择指引
- 03介绍 JWT 生命周期与存储实践
- 04比较认证提供方模式
- 05提供发布前安全检查清单
适用场景
- 01为新项目选择认证方案
- 02审查现有认证流程的风险
- 03迁移认证策略
- 04发布认证相关改动前检查安全项
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ zebbern/ claude-code-guide --skill authentication-patterns安装后在 Claude Code 中输入/authentication-patterns确认已加载。
详细介绍
身份验证模式技能
实现安全、适用于生产环境的身份验证参考。
WHEN_TO_USE
在项目中实现身份验证、审查现有身份验证流程的安全问题、选择身份验证提供商或迁移身份验证策略时,使用此技能。在发布任何身份验证相关更改之前,使用安全检查清单。
AUTH_APPROACHES
| 方式 | 工作原理 | 适用场景 | 缺点 |
|---|---|---|---|
| 基于会话 | 服务器在 DB/Redis 中存储会话,客户端通过 Cookie 持有会话 ID | 传统的服务器渲染应用,需要即时撤销会话的应用 | 需要服务器端存储;若没有共享存储,横向扩展较难 |
| JWT(无状态) | 服务器对令牌签名,客户端在每次请求中发送令牌 | API 优先的应用、微服务、移动客户端 | 无法在没有阻止列表的情况下撤销;令牌大小会随声明增加 |
| OAuth 2.0 / OIDC | 将身份验证委托给外部提供商(Google、GitHub 等) | 社交登录、企业 SSO、减少身份验证责任 | 流程更复杂,依赖外部提供商的可用性 |
| Passkeys / WebAuthn | 加密密钥对,无需密码 | 高安全性应用、无密码 UX | 对旧版浏览器的支持有限;需要进行用户教育 |
决策指南
- 具有简单需求的服务器渲染应用 → 基于会话
- 调用 API 的 SPA 或移动应用 → 使用刷新令牌轮换的 JWT
- 需要社交登录或 SSO → OAuth 2.0 / OIDC
- 从零开始且目标是现代 UX → Passkeys + OAuth 回退方案
JWT_BEST_PRACTICES
令牌生命周期
Login → Access Token (short-lived) + Refresh Token (long-lived, rotated)
│
├─ Access Token: 15 min expiry, sent via httpOnly cookie or Authorization header
│
└─ Refresh Token: 7-30 day expiry, stored in httpOnly secure cookie
│
└─ On use: issue new access + new refresh token, invalidate old refresh token
规则
- [P0-MUST] 为访问令牌设置较短的有效期(15 分钟或更短)。
- [P0-MUST] 将令牌存储在
httpOnly、Secure、SameSite=LaxCookie 中,绝不要存储在localStorage或sessionStorage中。 - [P0-MUST] 实现刷新令牌轮换,每个刷新令牌只能使用一次。
- [P0-MUST] 在服务器端维护已撤销刷新令牌的阻止列表。
- [P1-SHOULD] JWT 载荷中仅包含必要的声明(sub、iat、exp、role)。保持载荷精简。
- [P1-SHOULD] 分布式系统使用非对称签名(RS256 或 ES256);单服务场景使用对称签名(HS256)。
- [P1-SHOULD] 在每个请求中验证
iss、aud和exp声明。 - [P2-MAY] 当令牌载荷包含敏感数据时,使用 JWE(加密 JWT)。
令牌存储方式对比
| 存储方式 | 防 XSS | 防 CSRF | 建议 |
|---|---|---|---|
httpOnly Cookie | 是 | 否(需要 CSRF 令牌) | 推荐 |
localStorage | 否 | 是 | 绝不要用于存储身份验证令牌 |
sessionStorage | 否 | 是 | 绝不要用于存储身份验证令牌 |
| 内存中(JS 变量) | 是 | 是 | 适用于 SPA,但刷新后会丢失 |
PROVIDER_PATTERNS
对比
| 提供商 | 类型 | 适用场景 | 定价 | 主要功能 |
|---|---|---|---|---|
| NextAuth / Auth.js | 开源库 | 希望完全掌控的 Next.js 应用 | 免费 | 80+ 个提供商、DB 适配器、可自托管 |
| Clerk | 托管服务 | 快速上线、预构建 UI、用户管理 | 免费套餐,之后按 MAU 计费 | 即插即用组件、用户控制面板、组织支持 |
| Supabase Auth | 托管服务(Supabase 的一部分) | 已使用 Supabase 处理 DB/存储的应用 | 免费套餐,之后按 MAU 计费 | 行级安全集成、魔法链接、SSO |
| Lucia | 开源库 | 完全掌控、最少抽象 | 免费 | 基于会话、与框架无关、类型安全 |
何时使用各提供商
- NextAuth / Auth.js:需要灵活选择提供商、可自托管,并控制数据库会话。适用于需要自定义流程的场景。
- Clerk:希望通过预构建 UI 组件快速完成身份验证。适用于 MVP 和不想自行构建身份验证 UI 的团队。
- Supabase Auth:已经在使用 Supabase。身份验证可与 RLS 策略集成,实现行级安全。
- Lucia:需要一个精简、类型安全且不受框架限制的会话库。
NextAuth.js 设置模式
// app/api/auth/[...nextauth]/route.ts
import NextAuth from "next-auth";
import GitHub from "next-auth/providers/github";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { prisma } from "@/lib/prisma";
export const { handlers, auth, signIn, signOut } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [
GitHub({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
}),
],
callbacks: {
session({ session, user }) {
session.user.id = user.id;
return session;
},
},
});
SECURITY_CHECKLIST
发布身份验证前
- 速率限制:登录端点对每个 IP 每分钟限制 5-10 次尝试。
- CSRF 保护:对所有会更改状态的请求使用反 CSRF 令牌(或使用
SameSite=LaxCookie)。 - 密码哈希:使用 bcrypt(成本因子 12+)或 argon2id——绝不使用 MD5、SHA-1 或纯 SHA-256。
- 仅使用 HTTPS:所有身份验证端点均通过 TLS 提供服务。Cookie 带有
Secure标志。 - 输入验证:验证电子邮件格式、密码长度(最少 8 个字符,最多 128 个字符),并确保不存在 SQL/NoSQL 注入漏洞。
- 账户枚举:无论账户是否存在,登录和注册都返回相同的响应。
- 会话失效:注销时应使服务端会话/刷新令牌失效,而不只是清除客户端 Cookie。
- 支持 MFA:对敏感账户,将 TOTP(身份验证器应用)或 WebAuthn 用作第二重验证。
- 密码重置:令牌应有时限(1 小时)、只能使用一次,并通过安全通道发送。
- 审计日志:记录身份验证事件(登录、注销、失败尝试、密码更改),并包含时间戳和 IP。
密码哈希
// Using bcrypt
import bcrypt from "bcrypt";
const SALT_ROUNDS = 12;
async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, SALT_ROUNDS);
}
async function verifyPassword(password: string, hash: string): Promise<boolean> {
return bcrypt.compare(password, hash);
}
// Using argon2 (preferred for new projects)
import argon2 from "argon2";
async function hashPassword(password: string): Promise<string> {
return argon2.hash(password, { type: argon2.argon2id });
}
async function verifyPassword(hash: string, password: string): Promise<boolean> {
return argon2.verify(hash, password);
}
常见错误
| 错误 | 风险 | 修复方法 |
|---|---|---|
将 JWT 存储在 localStorage 中 | XSS 可窃取令牌 | 使用 httpOnly Cookie |
| JWT 有效期过长(数天/数周) | 被盗令牌会在较长时间内有效 | 使用 15 min 访问令牌 + 轮换刷新令牌 |
| 缺少 CSRF 保护 | 攻击者可从其他网站伪造请求 | 使用 SameSite=Lax Cookie + CSRF 令牌 |
| 密码要求过弱 | 暴力破解和凭据填充攻击 | 至少 8 个字符,并检查密码是否出现在已泄露密码列表中 |
| 登录时暴露用户是否存在 | 账户枚举 | 使用通用的“凭据无效”消息 |
| 不轮换刷新令牌 | 被盗的刷新令牌会授予无限期访问权限 | 使用单次有效的刷新令牌并进行轮换 |
| 在源代码中硬编码密钥 | 凭据可能通过 git 历史泄露 | 使用环境变量,绝不提交密钥 |
| 登录时缺少速率限制 | 暴力破解攻击 | 每个 IP 每分钟限制 5-10 次尝试,并采用指数退避 |
| 自行实现加密 | 存在不易察觉的漏洞 | 使用成熟的库(bcrypt、argon2、jose) |
| 不验证 JWT 声明 | 令牌可能在不同服务间被滥用 | 始终验证 iss、aud、exp |
评论
支持 Markdown