security-auth
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSecurity & Authentication Skill
安全与认证Skill
Model Selection
模型选择
Security-critical code requires Sonnet (not Haiku) due to:
- High risk of vulnerabilities
- Complex attack vectors
- Need for thorough validation
安全关键代码需使用Sonnet模型(而非Haiku),原因如下:
- 漏洞风险高
- 攻击向量复杂
- 需要全面验证
NextAuth Configuration
NextAuth配置
Session Configuration
会话配置
typescript
// Secure session settings
export const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: true, // HTTPS only in production
},
},
},
};typescript
// Secure session settings
export const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: `__Secure-next-auth.session-token`,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: true, // HTTPS only in production
},
},
},
};Protected Routes
受保护路由
typescript
// Server-side protection
import { getServerSession } from "next-auth";
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// ... protected logic
}typescript
// Server-side protection
import { getServerSession } from "next-auth";
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// ... protected logic
}Password Security
密码安全
Hashing
哈希处理
Always use bcrypt with sufficient rounds:
typescript
import bcrypt from "bcrypt";
const SALT_ROUNDS = 12; // Minimum 10, 12 recommended
// Hash password
const hashedPassword = await bcrypt.hash(plainPassword, SALT_ROUNDS);
// Verify password
const isValid = await bcrypt.compare(plainPassword, hashedPassword);始终使用足够轮次的bcrypt算法:
typescript
import bcrypt from "bcrypt";
const SALT_ROUNDS = 12; // Minimum 10, 12 recommended
// Hash password
const hashedPassword = await bcrypt.hash(plainPassword, SALT_ROUNDS);
// Verify password
const isValid = await bcrypt.compare(plainPassword, hashedPassword);Password Reset Flow
密码重置流程
- User requests reset (email)
- Generate secure token ()
crypto.randomBytes(32) - Store hashed token with expiration (1 hour max)
- Send reset link via email
- Validate token on reset page
- Hash new password, invalidate token
Critical:
- Never log tokens
- Rate limit reset requests
- Invalidate all sessions on password change
- 用户发起重置请求(通过邮件)
- 生成安全令牌()
crypto.randomBytes(32) - 存储带过期时间的哈希令牌(最长1小时)
- 通过邮件发送重置链接
- 在重置页面验证令牌
- 哈希新密码,使令牌失效
注意事项:
- 切勿记录令牌
- 对重置请求进行速率限制
- 密码修改后使所有会话失效
OWASP Top 10 Checklist
OWASP十大安全风险检查清单
1. Injection
1. 注入攻击
- Use parameterized queries (Prisma handles this)
- Validate all user input with Zod
- Never concatenate SQL strings
- 使用参数化查询(Prisma已处理此问题)
- 使用Zod验证所有用户输入
- 切勿拼接SQL字符串
2. Broken Authentication
2. 身份认证失效
- Strong password requirements
- Rate limit login attempts
- Secure session management
- Password hashing (bcrypt 12+ rounds)
- 强密码要求
- 对登录尝试进行速率限制
- 安全的会话管理
- 密码哈希处理(bcrypt 12+轮次)
3. Sensitive Data Exposure
3. 敏感数据泄露
- HTTPS everywhere
- Encrypt sensitive data at rest
- No secrets in code/logs
- Secure cookie flags
- 全站使用HTTPS
- 静态敏感数据加密存储
- 代码/日志中不包含密钥
- 安全的Cookie标记
4. XXE (XML External Entities)
4. XXE(XML外部实体)攻击
- Disable XML external entity processing
- Use JSON instead of XML where possible
- 禁用XML外部实体处理
- 尽可能使用JSON替代XML
5. Broken Access Control
5. 访问控制失效
- Verify authorization on every request
- Deny by default
- Log access control failures
- 对每个请求验证权限
- 默认拒绝所有访问
- 记录访问控制失败事件
6. Security Misconfiguration
6. 安全配置错误
- Remove default credentials
- Disable unnecessary features
- Keep dependencies updated
- 移除默认凭证
- 禁用不必要的功能
- 保持依赖更新
7. XSS (Cross-Site Scripting)
7. XSS(跨站脚本)攻击
- Never use
dangerouslySetInnerHTML - Escape user output
- Content Security Policy headers
- 切勿使用
dangerouslySetInnerHTML - 转义用户输出内容
- 配置内容安全策略响应头
8. Insecure Deserialization
8. 不安全的反序列化
- Validate serialized data
- Use type-safe serialization
- 验证序列化数据
- 使用类型安全的序列化方式
9. Using Components with Known Vulnerabilities
9. 使用存在已知漏洞的组件
- Run regularly
npm audit - Update dependencies
- Monitor security advisories
- 定期运行
npm audit - 更新依赖包
- 监控安全公告
10. Insufficient Logging & Monitoring
10. 日志与监控不足
- Log security events
- Monitor for anomalies
- Alerting on suspicious activity
- 记录安全事件
- 监控异常行为
- 对可疑活动设置告警
Rate Limiting
速率限制
API Route Rate Limiting
API路由速率限制
typescript
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
});
export async function POST(request: Request) {
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);
if (!success) {
return Response.json({ error: "Too many requests" }, { status: 429 });
}
// ... handle request
}typescript
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "10 s"),
});
export async function POST(request: Request) {
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);
if (!success) {
return Response.json({ error: "Too many requests" }, { status: 429 });
}
// ... handle request
}Sensitive Endpoint Limits
敏感接口限制
| Endpoint | Limit | Window |
|---|---|---|
| Login | 5 attempts | 15 min |
| Password reset | 3 requests | 1 hour |
| API general | 100 requests | 1 min |
| Registration | 3 accounts | 1 hour |
| 接口 | 限制次数 | 时间窗口 |
|---|---|---|
| 登录 | 5次尝试 | 15分钟 |
| 密码重置 | 3次请求 | 1小时 |
| 通用API | 100次请求 | 1分钟 |
| 注册 | 3个账号 | 1小时 |
Input Validation
输入验证
Always validate with Zod:
typescript
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
});
const resetSchema = z.object({
email: z.string().email(),
});
const newPasswordSchema = z.object({
token: z.string().min(32),
password: z
.string()
.min(8)
.max(128)
.regex(/[A-Z]/, "Must contain uppercase")
.regex(/[a-z]/, "Must contain lowercase")
.regex(/[0-9]/, "Must contain number"),
});始终使用Zod进行验证:
typescript
import { z } from "zod";
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(128),
});
const resetSchema = z.object({
email: z.string().email(),
});
const newPasswordSchema = z.object({
token: z.string().min(32),
password: z
.string()
.min(8)
.max(128)
.regex(/[A-Z]/, "Must contain uppercase")
.regex(/[a-z]/, "Must contain lowercase")
.regex(/[0-9]/, "Must contain number"),
});Security Headers
安全响应头
typescript
// next.config.ts
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
];typescript
// next.config.ts
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
];Security Review Checklist
安全评审检查清单
Before PR
提交PR前
- No hardcoded secrets
- All inputs validated
- Authorization checks in place
- Sensitive operations rate limited
- Error messages don't leak info
- Logs don't contain sensitive data
- 无硬编码密钥
- 所有输入均已验证
- 已设置权限校验
- 敏感操作已做速率限制
- 错误信息未泄露敏感信息
- 日志不包含敏感数据
Critical Code Patterns
关键代码规范
Never do:
typescript
// BAD - Hardcoded secret
const API_KEY = "sk-1234567890";
// BAD - SQL injection risk
const query = `SELECT * FROM users WHERE id = ${userId}`;
// BAD - XSS risk
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
// BAD - Timing attack vulnerability
if (token === storedToken) {
/* ... */
}Always do:
typescript
// GOOD - Environment variable
const API_KEY = process.env.API_KEY;
// GOOD - Parameterized (Prisma)
const user = await prisma.user.findUnique({ where: { id: userId } });
// GOOD - Escaped output
return <div>{userInput}</div>;
// GOOD - Constant-time comparison
import { timingSafeEqual } from "crypto";
if (timingSafeEqual(Buffer.from(token), Buffer.from(storedToken))) {
/* ... */
}切勿这样做:
typescript
// BAD - Hardcoded secret
const API_KEY = "sk-1234567890";
// BAD - SQL injection risk
const query = `SELECT * FROM users WHERE id = ${userId}`;
// BAD - XSS risk
return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
// BAD - Timing attack vulnerability
if (token === storedToken) {
/* ... */
}正确做法:
typescript
// GOOD - Environment variable
const API_KEY = process.env.API_KEY;
// GOOD - Parameterized (Prisma)
const user = await prisma.user.findUnique({ where: { id: userId } });
// GOOD - Escaped output
return <div>{userInput}</div>;
// GOOD - Constant-time comparison
import { timingSafeEqual } from "crypto";
if (timingSafeEqual(Buffer.from(token), Buffer.from(storedToken))) {
/* ... */
}Environment Variables
环境变量
Required for auth:
- - Random 32+ char string
NEXTAUTH_SECRET - - Full URL of app
NEXTAUTH_URL - - For password reset emails
RESEND_API_KEY
Never commit:
- files with real values
.env - API keys or tokens
- Database credentials
认证所需环境变量:
- - 32位以上的随机字符串
NEXTAUTH_SECRET - - 应用的完整URL
NEXTAUTH_URL - - 用于发送密码重置邮件
RESEND_API_KEY
切勿提交以下内容:
- 包含真实值的文件
.env - API密钥或令牌
- 数据库凭证