---
name: auth-implementation-patterns
description: 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.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

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

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

### Pattern 1: OAuth2 with Passport.js

```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)

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

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

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

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

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

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

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