Claude Agent Skill · by Wshobson

Auth Implementation Patterns

This walks you through the messy realities of auth implementation with actual code patterns for JWT refresh flows, session management with Redis, OAuth2 integra

Install
Terminal · npx
$npx skills add https://github.com/wshobson/agents --skill auth-implementation-patterns
Works with Paperclip

How Auth Implementation Patterns fits into a Paperclip company.

Auth Implementation Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md638 lines
Expand
---name: auth-implementation-patternsdescription: Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems. Use when implementing auth systems, securing APIs, or debugging security issues.--- # 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 ## Core Concepts ### 1. Authentication vs Authorization **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 ### 2. Authentication Strategies **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 ## JWT Authentication ### Pattern 1: JWT Implementation ```typescript// JWT structure: header.payload.signatureimport jwt from "jsonwebtoken";import { Request, Response, NextFunction } from "express"; interface JWTPayload {  userId: string;  email: string;  role: string;  iat: number;  exp: number;} // Generate JWTfunction 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 JWTfunction 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;  }} // Middlewarefunction 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" });  }} // Usageapp.get("/api/profile", authenticate, (req, res) => {  res.json({ user: req.user });});``` ### Pattern 2: Refresh Token Flow ```typescriptinterface 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 endpointsapp.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 ```typescriptimport session from "express-session";import RedisStore from "connect-redis";import { createClient } from "redis"; // Setup Redis for session storageconst 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    },  }),); // Loginapp.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 middlewarefunction requireAuth(req: Request, res: Response, next: NextFunction) {  if (!req.session.userId) {    return res.status(401).json({ error: "Not authenticated" });  }  next();} // Protected routeapp.get("/api/profile", requireAuth, async (req, res) => {  const user = await db.users.findById(req.session.userId);  res.json({ user });}); // Logoutapp.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 ### Pattern 1: OAuth2 with Passport.js ```typescriptimport passport from "passport";import { Strategy as GoogleStrategy } from "passport-google-oauth20";import { Strategy as GitHubStrategy } from "passport-github2"; // Google OAuthpassport.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);      }    },  ),); // Routesapp.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) ```typescriptenum 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);} // Middlewarefunction 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();  };} // Usageapp.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 ```typescriptenum 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();  };} // Usageapp.get(  "/api/users",  authenticate,  requirePermission(Permission.READ_USERS),  async (req, res) => {    const users = await db.users.findAll();    res.json({ users });  },);``` ### Pattern 3: Resource Ownership ```typescript// Check if user owns resourceasync 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();  };} // Usageapp.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 ```typescriptimport bcrypt from "bcrypt";import { z } from "zod"; // Password validation schemaconst 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 passwordasync function hashPassword(password: string): Promise<string> {  const saltRounds = 12; // 2^12 iterations  return bcrypt.hash(password, saltRounds);} // Verify passwordasync function verifyPassword(  password: string,  hash: string,): Promise<boolean> {  return bcrypt.compare(password, hash);} // Registration with password validationapp.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 ```typescriptimport rateLimit from "express-rate-limit";import RedisStore from "rate-limit-redis"; // Login rate limiterconst 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 limiterconst apiLimiter = rateLimit({  windowMs: 60 * 1000, // 1 minute  max: 100, // 100 requests per minute  standardHeaders: true,}); // Apply to routesapp.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/argon22. **Use HTTPS**: Encrypt data in transit3. **Short-Lived Access Tokens**: 15-30 minutes max4. **Secure Cookies**: httpOnly, secure, sameSite flags5. **Validate All Input**: Email format, password strength6. **Rate Limit Auth Endpoints**: Prevent brute force attacks7. **Implement CSRF Protection**: For session-based auth8. **Rotate Secrets Regularly**: JWT secrets, session secrets9. **Log Security Events**: Login attempts, failed auth10. **Use MFA When Possible**: Extra security layer ## 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