auth-implementation-patterns

Industry-standard authentication and authorization patterns for building secure, scalable access control systems. Covers JWT (with refresh token flow), session-based, and OAuth2/social login strategies with production-ready code examples Includes role-based access control (RBAC), permission-based authorization, and resource ownership validation patterns Provides password hashing with bcrypt, rate limiting, and security best practices including token expiration and secure cookie flags Demonstrates common pitfalls to avoid: weak passwords, client-only auth checks, missing token expiration, and unvalidated password resets

INSTALLATION
npx skills add https://github.com/wshobson/agents --skill auth-implementation-patterns
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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

// 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

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

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

Pattern 1: OAuth2 with Passport.js

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)

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

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

// 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

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

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

  • Never Store Plain Passwords: Always hash with bcrypt/argon2
  • Use HTTPS: Encrypt data in transit
  • Short-Lived Access Tokens: 15-30 minutes max
  • Secure Cookies: httpOnly, secure, sameSite flags
  • Validate All Input: Email format, password strength
  • Rate Limit Auth Endpoints: Prevent brute force attacks
  • Implement CSRF Protection: For session-based auth
  • Rotate Secrets Regularly: JWT secrets, session secrets
  • Log Security Events: Login attempts, failed auth
  • 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
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card