security-auth

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Security & 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

密码重置流程

  1. User requests reset (email)
  2. Generate secure token (
    crypto.randomBytes(32)
    )
  3. Store hashed token with expiration (1 hour max)
  4. Send reset link via email
  5. Validate token on reset page
  6. Hash new password, invalidate token
Critical:
  • Never log tokens
  • Rate limit reset requests
  • Invalidate all sessions on password change
  1. 用户发起重置请求(通过邮件)
  2. 生成安全令牌(
    crypto.randomBytes(32)
  3. 存储带过期时间的哈希令牌(最长1小时)
  4. 通过邮件发送重置链接
  5. 在重置页面验证令牌
  6. 哈希新密码,使令牌失效
注意事项:
  • 切勿记录令牌
  • 对重置请求进行速率限制
  • 密码修改后使所有会话失效

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
    npm audit
    regularly
  • 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

敏感接口限制

EndpointLimitWindow
Login5 attempts15 min
Password reset3 requests1 hour
API general100 requests1 min
Registration3 accounts1 hour
接口限制次数时间窗口
登录5次尝试15分钟
密码重置3次请求1小时
通用API100次请求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:
  • NEXTAUTH_SECRET
    - Random 32+ char string
  • NEXTAUTH_URL
    - Full URL of app
  • RESEND_API_KEY
    - For password reset emails
Never commit:
  • .env
    files with real values
  • API keys or tokens
  • Database credentials
认证所需环境变量:
  • NEXTAUTH_SECRET
    - 32位以上的随机字符串
  • NEXTAUTH_URL
    - 应用的完整URL
  • RESEND_API_KEY
    - 用于发送密码重置邮件
切勿提交以下内容:
  • 包含真实值的
    .env
    文件
  • API密钥或令牌
  • 数据库凭证