auth-implementation-patterns

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Authentication & Authorization Implementation Patterns

身份验证与授权实现模式

Build secure, scalable authentication and authorization systems using industry-standard patterns and modern best practices.
使用行业标准模式和现代最佳实践,构建安全、可扩展的身份验证与授权系统。

When to Use This Skill

适用场景

  • Implementing user authentication systems
  • Securing REST or GraphQL APIs
  • Adding OAuth2/social login
  • Implementing role-based access control (RBAC)
  • Designing session management
  • Migrating authentication systems
  • Debugging auth issues
  • Implementing SSO or multi-tenancy
  • 实现用户身份验证系统
  • 保护REST或GraphQL API
  • 添加OAuth2/社交登录功能
  • 实现基于角色的访问控制(RBAC)
  • 设计会话管理机制
  • 迁移身份验证系统
  • 调试认证相关问题
  • 实现SSO或多租户架构

Core Concepts

核心概念

1. Authentication vs Authorization

1. 身份验证 vs 授权

Authentication (AuthN): Who are you?
  • Verifying identity (username/password, OAuth, biometrics)
  • Issuing credentials (sessions, tokens)
  • Managing login/logout
Authorization (AuthZ): What can you do?
  • Permission checking
  • Role-based access control (RBAC)
  • Resource ownership validation
  • Policy enforcement
身份验证(AuthN):你是谁?
  • 验证身份(用户名/密码、OAuth、生物识别等)
  • 颁发凭证(会话、令牌)
  • 管理登录/登出流程
授权(AuthZ):你能做什么?
  • 权限检查
  • 基于角色的访问控制(RBAC)
  • 资源所有权验证
  • 策略强制执行

2. Authentication Strategies

2. 身份验证策略

Session-Based:
  • Server stores session state
  • Session ID in cookie
  • Traditional, simple, stateful
Token-Based (JWT):
  • Stateless, self-contained
  • Scales horizontally
  • Can store claims
OAuth2/OpenID Connect:
  • Delegate authentication
  • Social login (Google, GitHub)
  • Enterprise SSO
基于会话的认证:
  • 服务器存储会话状态
  • Cookie中存储会话ID
  • 传统方案,简单易用,有状态
基于令牌的认证(JWT):
  • 无状态,自包含
  • 支持水平扩展
  • 可存储声明信息
OAuth2/OpenID Connect:
  • 委托身份验证
  • 社交登录(Google、GitHub等)
  • 企业级SSO

JWT Authentication

JWT身份验证

Pattern 1: JWT Implementation

模式1:JWT实现

typescript
// JWT structure: header.payload.signature
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";

interface JWTPayload {
  userId: string;
  email: string;
  role: string;
  iat: number;
  exp: number;
}

// Generate JWT
function generateTokens(userId: string, email: string, role: string) {
  const accessToken = jwt.sign(
    { userId, email, role },
    process.env.JWT_SECRET!,
    { expiresIn: "15m" }, // Short-lived
  );

  const refreshToken = jwt.sign(
    { userId },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: "7d" }, // Long-lived
  );

  return { accessToken, refreshToken };
}

// Verify JWT
function verifyToken(token: string): JWTPayload {
  try {
    return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      throw new Error("Token expired");
    }
    if (error instanceof jwt.JsonWebTokenError) {
      throw new Error("Invalid token");
    }
    throw error;
  }
}

// Middleware
function authenticate(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "No token provided" });
  }

  const token = authHeader.substring(7);
  try {
    const payload = verifyToken(token);
    req.user = payload; // Attach user to request
    next();
  } catch (error) {
    return res.status(401).json({ error: "Invalid token" });
  }
}

// Usage
app.get("/api/profile", authenticate, (req, res) => {
  res.json({ user: req.user });
});
typescript
// JWT structure: header.payload.signature
import jwt from "jsonwebtoken";
import { Request, Response, NextFunction } from "express";

interface JWTPayload {
  userId: string;
  email: string;
  role: string;
  iat: number;
  exp: number;
}

// Generate JWT
function generateTokens(userId: string, email: string, role: string) {
  const accessToken = jwt.sign(
    { userId, email, role },
    process.env.JWT_SECRET!,
    { expiresIn: "15m" }, // Short-lived
  );

  const refreshToken = jwt.sign(
    { userId },
    process.env.JWT_REFRESH_SECRET!,
    { expiresIn: "7d" }, // Long-lived
  );

  return { accessToken, refreshToken };
}

// Verify JWT
function verifyToken(token: string): JWTPayload {
  try {
    return jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      throw new Error("Token expired");
    }
    if (error instanceof jwt.JsonWebTokenError) {
      throw new Error("Invalid token");
    }
    throw error;
  }
}

// Middleware
function authenticate(req: Request, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;
  if (!authHeader?.startsWith("Bearer ")) {
    return res.status(401).json({ error: "No token provided" });
  }

  const token = authHeader.substring(7);
  try {
    const payload = verifyToken(token);
    req.user = payload; // Attach user to request
    next();
  } catch (error) {
    return res.status(401).json({ error: "Invalid token" });
  }
}

// Usage
app.get("/api/profile", authenticate, (req, res) => {
  res.json({ user: req.user });
});

Pattern 2: Refresh Token Flow

模式2:刷新令牌流程

typescript
interface StoredRefreshToken {
  token: string;
  userId: string;
  expiresAt: Date;
  createdAt: Date;
}

class RefreshTokenService {
  // Store refresh token in database
  async storeRefreshToken(userId: string, refreshToken: string) {
    const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
    await db.refreshTokens.create({
      token: await hash(refreshToken), // Hash before storing
      userId,
      expiresAt,
    });
  }

  // Refresh access token
  async refreshAccessToken(refreshToken: string) {
    // Verify refresh token
    let payload;
    try {
      payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as {
        userId: string;
      };
    } catch {
      throw new Error("Invalid refresh token");
    }

    // Check if token exists in database
    const storedToken = await db.refreshTokens.findOne({
      where: {
        token: await hash(refreshToken),
        userId: payload.userId,
        expiresAt: { $gt: new Date() },
      },
    });

    if (!storedToken) {
      throw new Error("Refresh token not found or expired");
    }

    // Get user
    const user = await db.users.findById(payload.userId);
    if (!user) {
      throw new Error("User not found");
    }

    // Generate new access token
    const accessToken = jwt.sign(
      { userId: user.id, email: user.email, role: user.role },
      process.env.JWT_SECRET!,
      { expiresIn: "15m" },
    );

    return { accessToken };
  }

  // Revoke refresh token (logout)
  async revokeRefreshToken(refreshToken: string) {
    await db.refreshTokens.deleteOne({
      token: await hash(refreshToken),
    });
  }

  // Revoke all user tokens (logout all devices)
  async revokeAllUserTokens(userId: string) {
    await db.refreshTokens.deleteMany({ userId });
  }
}

// API endpoints
app.post("/api/auth/refresh", async (req, res) => {
  const { refreshToken } = req.body;
  try {
    const { accessToken } =
      await refreshTokenService.refreshAccessToken(refreshToken);
    res.json({ accessToken });
  } catch (error) {
    res.status(401).json({ error: "Invalid refresh token" });
  }
});

app.post("/api/auth/logout", authenticate, async (req, res) => {
  const { refreshToken } = req.body;
  await refreshTokenService.revokeRefreshToken(refreshToken);
  res.json({ message: "Logged out successfully" });
});
typescript
interface StoredRefreshToken {
  token: string;
  userId: string;
  expiresAt: Date;
  createdAt: Date;
}

class RefreshTokenService {
  // Store refresh token in database
  async storeRefreshToken(userId: string, refreshToken: string) {
    const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
    await db.refreshTokens.create({
      token: await hash(refreshToken), // Hash before storing
      userId,
      expiresAt,
    });
  }

  // Refresh access token
  async refreshAccessToken(refreshToken: string) {
    // Verify refresh token
    let payload;
    try {
      payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!) as {
        userId: string;
      };
    } catch {
      throw new Error("Invalid refresh token");
    }

    // Check if token exists in database
    const storedToken = await db.refreshTokens.findOne({
      where: {
        token: await hash(refreshToken),
        userId: payload.userId,
        expiresAt: { $gt: new Date() },
      },
    });

    if (!storedToken) {
      throw new Error("Refresh token not found or expired");
    }

    // Get user
    const user = await db.users.findById(payload.userId);
    if (!user) {
      throw new Error("User not found");
    }

    // Generate new access token
    const accessToken = jwt.sign(
      { userId: user.id, email: user.email, role: user.role },
      process.env.JWT_SECRET!,
      { expiresIn: "15m" },
    );

    return { accessToken };
  }

  // Revoke refresh token (logout)
  async revokeRefreshToken(refreshToken: string) {
    await db.refreshTokens.deleteOne({
      token: await hash(refreshToken),
    });
  }

  // Revoke all user tokens (logout all devices)
  async revokeAllUserTokens(userId: string) {
    await db.refreshTokens.deleteMany({ userId });
  }
}

// API endpoints
app.post("/api/auth/refresh", async (req, res) => {
  const { refreshToken } = req.body;
  try {
    const { accessToken } =
      await refreshTokenService.refreshAccessToken(refreshToken);
    res.json({ accessToken });
  } catch (error) {
    res.status(401).json({ error: "Invalid refresh token" });
  }
});

app.post("/api/auth/logout", authenticate, async (req, res) => {
  const { refreshToken } = req.body;
  await refreshTokenService.revokeRefreshToken(refreshToken);
  res.json({ message: "Logged out successfully" });
});

Session-Based Authentication

基于会话的身份验证

Pattern 1: Express Session

模式1:Express Session实现

typescript
import session from "express-session";
import RedisStore from "connect-redis";
import { createClient } from "redis";

// Setup Redis for session storage
const redisClient = createClient({
  url: process.env.REDIS_URL,
});
await redisClient.connect();

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: process.env.NODE_ENV === "production", // HTTPS only
      httpOnly: true, // No JavaScript access
      maxAge: 24 * 60 * 60 * 1000, // 24 hours
      sameSite: "strict", // CSRF protection
    },
  }),
);

// Login
app.post("/api/auth/login", async (req, res) => {
  const { email, password } = req.body;

  const user = await db.users.findOne({ email });
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  // Store user in session
  req.session.userId = user.id;
  req.session.role = user.role;

  res.json({ user: { id: user.id, email: user.email, role: user.role } });
});

// Session middleware
function requireAuth(req: Request, res: Response, next: NextFunction) {
  if (!req.session.userId) {
    return res.status(401).json({ error: "Not authenticated" });
  }
  next();
}

// Protected route
app.get("/api/profile", requireAuth, async (req, res) => {
  const user = await db.users.findById(req.session.userId);
  res.json({ user });
});

// Logout
app.post("/api/auth/logout", (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: "Logout failed" });
    }
    res.clearCookie("connect.sid");
    res.json({ message: "Logged out successfully" });
  });
});
typescript
import session from "express-session";
import RedisStore from "connect-redis";
import { createClient } from "redis";

// Setup Redis for session storage
const redisClient = createClient({
  url: process.env.REDIS_URL,
});
await redisClient.connect();

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET!,
    resave: false,
    saveUninitialized: false,
    cookie: {
      secure: process.env.NODE_ENV === "production", // HTTPS only
      httpOnly: true, // No JavaScript access
      maxAge: 24 * 60 * 60 * 1000, // 24 hours
      sameSite: "strict", // CSRF protection
    },
  }),
);

// Login
app.post("/api/auth/login", async (req, res) => {
  const { email, password } = req.body;

  const user = await db.users.findOne({ email });
  if (!user || !(await verifyPassword(password, user.passwordHash))) {
    return res.status(401).json({ error: "Invalid credentials" });
  }

  // Store user in session
  req.session.userId = user.id;
  req.session.role = user.role;

  res.json({ user: { id: user.id, email: user.email, role: user.role } });
});

// Session middleware
function requireAuth(req: Request, res: Response, next: NextFunction) {
  if (!req.session.userId) {
    return res.status(401).json({ error: "Not authenticated" });
  }
  next();
}

// Protected route
app.get("/api/profile", requireAuth, async (req, res) => {
  const user = await db.users.findById(req.session.userId);
  res.json({ user });
});

// Logout
app.post("/api/auth/logout", (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: "Logout failed" });
    }
    res.clearCookie("connect.sid");
    res.json({ message: "Logged out successfully" });
  });
});

OAuth2 / Social Login

OAuth2 / 社交登录

Pattern 1: OAuth2 with Passport.js

模式1:使用Passport.js实现OAuth2

typescript
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { Strategy as GitHubStrategy } from "passport-github2";

// Google OAuth
passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      callbackURL: "/api/auth/google/callback",
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        // Find or create user
        let user = await db.users.findOne({
          googleId: profile.id,
        });

        if (!user) {
          user = await db.users.create({
            googleId: profile.id,
            email: profile.emails?.[0]?.value,
            name: profile.displayName,
            avatar: profile.photos?.[0]?.value,
          });
        }

        return done(null, user);
      } catch (error) {
        return done(error, undefined);
      }
    },
  ),
);

// Routes
app.get(
  "/api/auth/google",
  passport.authenticate("google", {
    scope: ["profile", "email"],
  }),
);

app.get(
  "/api/auth/google/callback",
  passport.authenticate("google", { session: false }),
  (req, res) => {
    // Generate JWT
    const tokens = generateTokens(req.user.id, req.user.email, req.user.role);
    // Redirect to frontend with token
    res.redirect(
      `${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`,
    );
  },
);
typescript
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
import { Strategy as GitHubStrategy } from "passport-github2";

// Google OAuth
passport.use(
  new GoogleStrategy(
    {
      clientID: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      callbackURL: "/api/auth/google/callback",
    },
    async (accessToken, refreshToken, profile, done) => {
      try {
        // Find or create user
        let user = await db.users.findOne({
          googleId: profile.id,
        });

        if (!user) {
          user = await db.users.create({
            googleId: profile.id,
            email: profile.emails?.[0]?.value,
            name: profile.displayName,
            avatar: profile.photos?.[0]?.value,
          });
        }

        return done(null, user);
      } catch (error) {
        return done(error, undefined);
      }
    },
  ),
);

// Routes
app.get(
  "/api/auth/google",
  passport.authenticate("google", {
    scope: ["profile", "email"],
  }),
);

app.get(
  "/api/auth/google/callback",
  passport.authenticate("google", { session: false }),
  (req, res) => {
    // Generate JWT
    const tokens = generateTokens(req.user.id, req.user.email, req.user.role);
    // Redirect to frontend with token
    res.redirect(
      `${process.env.FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`,
    );
  },
);

Authorization Patterns

授权模式

Pattern 1: Role-Based Access Control (RBAC)

模式1:基于角色的访问控制(RBAC)

typescript
enum Role {
  USER = "user",
  MODERATOR = "moderator",
  ADMIN = "admin",
}

const roleHierarchy: Record<Role, Role[]> = {
  [Role.ADMIN]: [Role.ADMIN, Role.MODERATOR, Role.USER],
  [Role.MODERATOR]: [Role.MODERATOR, Role.USER],
  [Role.USER]: [Role.USER],
};

function hasRole(userRole: Role, requiredRole: Role): boolean {
  return roleHierarchy[userRole].includes(requiredRole);
}

// Middleware
function requireRole(...roles: Role[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: "Not authenticated" });
    }

    if (!roles.some((role) => hasRole(req.user.role, role))) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }

    next();
  };
}

// Usage
app.delete(
  "/api/users/:id",
  authenticate,
  requireRole(Role.ADMIN),
  async (req, res) => {
    // Only admins can delete users
    await db.users.delete(req.params.id);
    res.json({ message: "User deleted" });
  },
);
typescript
enum Role {
  USER = "user",
  MODERATOR = "moderator",
  ADMIN = "admin",
}

const roleHierarchy: Record<Role, Role[]> = {
  [Role.ADMIN]: [Role.ADMIN, Role.MODERATOR, Role.USER],
  [Role.MODERATOR]: [Role.MODERATOR, Role.USER],
  [Role.USER]: [Role.USER],
};

function hasRole(userRole: Role, requiredRole: Role): boolean {
  return roleHierarchy[userRole].includes(requiredRole);
}

// Middleware
function requireRole(...roles: Role[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: "Not authenticated" });
    }

    if (!roles.some((role) => hasRole(req.user.role, role))) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }

    next();
  };
}

// Usage
app.delete(
  "/api/users/:id",
  authenticate,
  requireRole(Role.ADMIN),
  async (req, res) => {
    // Only admins can delete users
    await db.users.delete(req.params.id);
    res.json({ message: "User deleted" });
  },
);

Pattern 2: Permission-Based Access Control

模式2:基于权限的访问控制

typescript
enum Permission {
  READ_USERS = "read:users",
  WRITE_USERS = "write:users",
  DELETE_USERS = "delete:users",
  READ_POSTS = "read:posts",
  WRITE_POSTS = "write:posts",
}

const rolePermissions: Record<Role, Permission[]> = {
  [Role.USER]: [Permission.READ_POSTS, Permission.WRITE_POSTS],
  [Role.MODERATOR]: [
    Permission.READ_POSTS,
    Permission.WRITE_POSTS,
    Permission.READ_USERS,
  ],
  [Role.ADMIN]: Object.values(Permission),
};

function hasPermission(userRole: Role, permission: Permission): boolean {
  return rolePermissions[userRole]?.includes(permission) ?? false;
}

function requirePermission(...permissions: Permission[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: "Not authenticated" });
    }

    const hasAllPermissions = permissions.every((permission) =>
      hasPermission(req.user.role, permission),
    );

    if (!hasAllPermissions) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }

    next();
  };
}

// Usage
app.get(
  "/api/users",
  authenticate,
  requirePermission(Permission.READ_USERS),
  async (req, res) => {
    const users = await db.users.findAll();
    res.json({ users });
  },
);
typescript
enum Permission {
  READ_USERS = "read:users",
  WRITE_USERS = "write:users",
  DELETE_USERS = "delete:users",
  READ_POSTS = "read:posts",
  WRITE_POSTS = "write:posts",
}

const rolePermissions: Record<Role, Permission[]> = {
  [Role.USER]: [Permission.READ_POSTS, Permission.WRITE_POSTS],
  [Role.MODERATOR]: [
    Permission.READ_POSTS,
    Permission.WRITE_POSTS,
    Permission.READ_USERS,
  ],
  [Role.ADMIN]: Object.values(Permission),
};

function hasPermission(userRole: Role, permission: Permission): boolean {
  return rolePermissions[userRole]?.includes(permission) ?? false;
}

function requirePermission(...permissions: Permission[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: "Not authenticated" });
    }

    const hasAllPermissions = permissions.every((permission) =>
      hasPermission(req.user.role, permission),
    );

    if (!hasAllPermissions) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }

    next();
  };
}

// Usage
app.get(
  "/api/users",
  authenticate,
  requirePermission(Permission.READ_USERS),
  async (req, res) => {
    const users = await db.users.findAll();
    res.json({ users });
  },
);

Pattern 3: Resource Ownership

模式3:资源所有权验证

typescript
// Check if user owns resource
async function requireOwnership(
  resourceType: "post" | "comment",
  resourceIdParam: string = "id",
) {
  return async (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: "Not authenticated" });
    }

    const resourceId = req.params[resourceIdParam];

    // Admins can access anything
    if (req.user.role === Role.ADMIN) {
      return next();
    }

    // Check ownership
    let resource;
    if (resourceType === "post") {
      resource = await db.posts.findById(resourceId);
    } else if (resourceType === "comment") {
      resource = await db.comments.findById(resourceId);
    }

    if (!resource) {
      return res.status(404).json({ error: "Resource not found" });
    }

    if (resource.userId !== req.user.userId) {
      return res.status(403).json({ error: "Not authorized" });
    }

    next();
  };
}

// Usage
app.put(
  "/api/posts/:id",
  authenticate,
  requireOwnership("post"),
  async (req, res) => {
    // User can only update their own posts
    const post = await db.posts.update(req.params.id, req.body);
    res.json({ post });
  },
);
typescript
// Check if user owns resource
async function requireOwnership(
  resourceType: "post" | "comment",
  resourceIdParam: string = "id",
) {
  return async (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) {
      return res.status(401).json({ error: "Not authenticated" });
    }

    const resourceId = req.params[resourceIdParam];

    // Admins can access anything
    if (req.user.role === Role.ADMIN) {
      return next();
    }

    // Check ownership
    let resource;
    if (resourceType === "post") {
      resource = await db.posts.findById(resourceId);
    } else if (resourceType === "comment") {
      resource = await db.comments.findById(resourceId);
    }

    if (!resource) {
      return res.status(404).json({ error: "Resource not found" });
    }

    if (resource.userId !== req.user.userId) {
      return res.status(403).json({ error: "Not authorized" });
    }

    next();
  };
}

// Usage
app.put(
  "/api/posts/:id",
  authenticate,
  requireOwnership("post"),
  async (req, res) => {
    // User can only update their own posts
    const post = await db.posts.update(req.params.id, req.body);
    res.json({ post });
  },
);

Security Best Practices

安全最佳实践

Pattern 1: Password Security

模式1:密码安全

typescript
import bcrypt from "bcrypt";
import { z } from "zod";

// Password validation schema
const passwordSchema = z
  .string()
  .min(12, "Password must be at least 12 characters")
  .regex(/[A-Z]/, "Password must contain uppercase letter")
  .regex(/[a-z]/, "Password must contain lowercase letter")
  .regex(/[0-9]/, "Password must contain number")
  .regex(/[^A-Za-z0-9]/, "Password must contain special character");

// Hash password
async function hashPassword(password: string): Promise<string> {
  const saltRounds = 12; // 2^12 iterations
  return bcrypt.hash(password, saltRounds);
}

// Verify password
async function verifyPassword(
  password: string,
  hash: string,
): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// Registration with password validation
app.post("/api/auth/register", async (req, res) => {
  try {
    const { email, password } = req.body;

    // Validate password
    passwordSchema.parse(password);

    // Check if user exists
    const existingUser = await db.users.findOne({ email });
    if (existingUser) {
      return res.status(400).json({ error: "Email already registered" });
    }

    // Hash password
    const passwordHash = await hashPassword(password);

    // Create user
    const user = await db.users.create({
      email,
      passwordHash,
    });

    // Generate tokens
    const tokens = generateTokens(user.id, user.email, user.role);

    res.status(201).json({
      user: { id: user.id, email: user.email },
      ...tokens,
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({ error: error.errors[0].message });
    }
    res.status(500).json({ error: "Registration failed" });
  }
});
typescript
import bcrypt from "bcrypt";
import { z } from "zod";

// Password validation schema
const passwordSchema = z
  .string()
  .min(12, "Password must be at least 12 characters")
  .regex(/[A-Z]/, "Password must contain uppercase letter")
  .regex(/[a-z]/, "Password must contain lowercase letter")
  .regex(/[0-9]/, "Password must contain number")
  .regex(/[^A-Za-z0-9]/, "Password must contain special character");

// Hash password
async function hashPassword(password: string): Promise<string> {
  const saltRounds = 12; // 2^12 iterations
  return bcrypt.hash(password, saltRounds);
}

// Verify password
async function verifyPassword(
  password: string,
  hash: string,
): Promise<boolean> {
  return bcrypt.compare(password, hash);
}

// Registration with password validation
app.post("/api/auth/register", async (req, res) => {
  try {
    const { email, password } = req.body;

    // Validate password
    passwordSchema.parse(password);

    // Check if user exists
    const existingUser = await db.users.findOne({ email });
    if (existingUser) {
      return res.status(400).json({ error: "Email already registered" });
    }

    // Hash password
    const passwordHash = await hashPassword(password);

    // Create user
    const user = await db.users.create({
      email,
      passwordHash,
    });

    // Generate tokens
    const tokens = generateTokens(user.id, user.email, user.role);

    res.status(201).json({
      user: { id: user.id, email: user.email },
      ...tokens,
    });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return res.status(400).json({ error: error.errors[0].message });
    }
    res.status(500).json({ error: "Registration failed" });
  }
});

Pattern 2: Rate Limiting

模式2:速率限制

typescript
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";

// Login rate limiter
const loginLimiter = rateLimit({
  store: new RedisStore({ client: redisClient }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts
  message: "Too many login attempts, please try again later",
  standardHeaders: true,
  legacyHeaders: false,
});

// API rate limiter
const apiLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100, // 100 requests per minute
  standardHeaders: true,
});

// Apply to routes
app.post("/api/auth/login", loginLimiter, async (req, res) => {
  // Login logic
});

app.use("/api/", apiLimiter);
typescript
import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";

// Login rate limiter
const loginLimiter = rateLimit({
  store: new RedisStore({ client: redisClient }),
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 5, // 5 attempts
  message: "Too many login attempts, please try again later",
  standardHeaders: true,
  legacyHeaders: false,
});

// API rate limiter
const apiLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100, // 100 requests per minute
  standardHeaders: true,
});

// Apply to routes
app.post("/api/auth/login", loginLimiter, async (req, res) => {
  // Login logic
});

app.use("/api/", apiLimiter);

Best Practices

最佳实践

  1. Never Store Plain Passwords: Always hash with bcrypt/argon2
  2. Use HTTPS: Encrypt data in transit
  3. Short-Lived Access Tokens: 15-30 minutes max
  4. Secure Cookies: httpOnly, secure, sameSite flags
  5. Validate All Input: Email format, password strength
  6. Rate Limit Auth Endpoints: Prevent brute force attacks
  7. Implement CSRF Protection: For session-based auth
  8. Rotate Secrets Regularly: JWT secrets, session secrets
  9. Log Security Events: Login attempts, failed auth
  10. Use MFA When Possible: Extra security layer
  1. 绝不存储明文密码:始终使用bcrypt/argon2进行哈希
  2. 使用HTTPS:加密传输中的数据
  3. 短有效期访问令牌:最长15-30分钟
  4. 安全Cookie设置:启用httpOnly、secure、sameSite标志
  5. 验证所有输入:邮箱格式、密码强度
  6. 对认证端点进行速率限制:防止暴力破解攻击
  7. 实现CSRF保护:针对基于会话的认证
  8. 定期轮换密钥:JWT密钥、会话密钥
  9. 记录安全事件:登录尝试、失败的认证操作
  10. 尽可能使用MFA:增加额外安全层

Common Pitfalls

常见陷阱

  • Weak Passwords: Enforce strong password policies
  • JWT in localStorage: Vulnerable to XSS, use httpOnly cookies
  • No Token Expiration: Tokens should expire
  • Client-Side Auth Checks Only: Always validate server-side
  • Insecure Password Reset: Use secure tokens with expiration
  • No Rate Limiting: Vulnerable to brute force
  • Trusting Client Data: Always validate on server
  • 弱密码:强制执行强密码策略
  • JWT存储在localStorage:易受XSS攻击,建议使用httpOnly Cookie
  • 令牌无有效期:令牌必须设置过期时间
  • 仅在客户端进行认证检查:始终在服务端验证
  • 不安全的密码重置流程:使用带有效期的安全令牌
  • 未设置速率限制:易受暴力破解
  • 信任客户端数据:始终在服务端验证数据

Resources

资源

  • references/jwt-best-practices.md: JWT implementation guide
  • references/oauth2-flows.md: OAuth2 flow diagrams and examples
  • references/session-security.md: Secure session management
  • assets/auth-security-checklist.md: Security review checklist
  • assets/password-policy-template.md: Password requirements template
  • scripts/token-validator.ts: JWT validation utility
  • references/jwt-best-practices.md:JWT实现指南
  • references/oauth2-flows.md:OAuth2流程示意图及示例
  • references/session-security.md:安全会话管理
  • assets/auth-security-checklist.md:安全审查清单
  • assets/password-policy-template.md:密码要求模板
  • scripts/token-validator.ts:JWT验证工具