Назад към всички

secure-auth-patterns

// Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems.

$ git log --oneline --stat
stars:1,933
forks:367
updated:March 4, 2026
SKILL.mdreadonly

Authentication & Authorization Patterns

Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems.

Description

USE WHEN:

  • Implementing user authentication systems
  • Securing REST or GraphQL APIs
  • Adding OAuth2/social login or SSO
  • Designing session management
  • Implementing RBAC or permission systems
  • Debugging authentication issues

DON'T USE WHEN:

  • Only need UI/login page styling
  • Task is infrastructure-only without identity concerns
  • Cannot change auth policies

Authentication vs Authorization

AuthN (Authentication)AuthZ (Authorization)
"Who are you?""What can you do?"
Verify identityCheck permissions
Issue credentialsEnforce policies
Login/logoutAccess control

Authentication Strategies

StrategyProsConsBest For
SessionSimple, secureStateful, scalingTraditional web apps
JWTStateless, scalableToken size, revocationAPIs, microservices
OAuth2/OIDCDelegated, SSOComplex setupSocial login, enterprise

JWT Implementation

Generate Tokens

import jwt from 'jsonwebtoken';

function generateTokens(user: User) {
  const accessToken = jwt.sign(
    { userId: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET!,
    { expiresIn: '15m' }  // Short-lived
  );

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

  return { accessToken, refreshToken };
}

Verify 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 = jwt.verify(token, process.env.JWT_SECRET!);
    req.user = payload;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

Refresh Token Flow

app.post('/api/auth/refresh', async (req, res) => {
  const { refreshToken } = req.body;
  
  try {
    // Verify refresh token
    const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!);
    
    // Check if token exists in database (not revoked)
    const storedToken = await db.refreshTokens.findOne({
      token: await hash(refreshToken),
      expiresAt: { $gt: new Date() }
    });
    
    if (!storedToken) {
      return res.status(403).json({ error: 'Token revoked' });
    }
    
    // Generate new access token
    const user = await db.users.findById(payload.userId);
    const accessToken = jwt.sign(
      { userId: user.id, email: user.email, role: user.role },
      process.env.JWT_SECRET!,
      { expiresIn: '15m' }
    );
    
    res.json({ accessToken });
  } catch {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

Session-Based Authentication

import session from 'express-session';
import RedisStore from 'connect-redis';

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' });
  }
  
  req.session.userId = user.id;
  req.session.role = user.role;
  res.json({ user: { id: user.id, email: user.email } });
});

// Logout
app.post('/api/auth/logout', (req, res) => {
  req.session.destroy(() => {
    res.clearCookie('connect.sid');
    res.json({ message: 'Logged out' });
  });
});

OAuth2 / Social Login

import passport from 'passport';
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';

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) => {
  // 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
    });
  }
  
  return done(null, user);
}));

// 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) => {
    const tokens = generateTokens(req.user);
    res.redirect(`${FRONTEND_URL}/auth/callback?token=${tokens.accessToken}`);
  });

Authorization: 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);
}

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) => {
    await db.users.delete(req.params.id);
    res.json({ message: 'User deleted' });
  }
);

Permission-Based Access

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 requirePermission(...permissions: Permission[]) {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
    
    const hasAll = permissions.every(p => 
      rolePermissions[req.user.role]?.includes(p)
    );
    
    if (!hasAll) return res.status(403).json({ error: 'Insufficient permissions' });
    next();
  };
}

Resource Ownership

function requireOwnership(resourceType: 'post' | 'comment') {
  return async (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
    
    // Admins can access anything
    if (req.user.role === Role.ADMIN) return next();
    
    const resource = await db[resourceType].findById(req.params.id);
    if (!resource) return res.status(404).json({ error: 'Not found' });
    
    if (resource.userId !== req.user.userId) {
      return res.status(403).json({ error: 'Not authorized' });
    }
    
    next();
  };
}

// Usage: Users can only update their own posts
app.put('/api/posts/:id', authenticate, requireOwnership('post'), updatePost);

Password Security

import bcrypt from 'bcrypt';
import { z } from 'zod';

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

async function hashPassword(password: string): Promise<string> {
  return bcrypt.hash(password, 12);  // 12 rounds
}

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

Best Practices

✅ Do

  • Use HTTPS everywhere
  • Hash passwords with bcrypt (12+ rounds)
  • Use short-lived access tokens (15-30 min)
  • Store refresh tokens in database (revocable)
  • Validate all input
  • Rate limit auth endpoints
  • Log security events
  • Use secure cookie flags (httpOnly, secure, sameSite)

❌ Don't

  • Store passwords in plain text
  • Store JWT in localStorage (XSS vulnerable)
  • Use weak JWT secrets
  • Trust client-side auth checks only
  • Expose stack traces in errors
  • Skip server-side validation
  • Ignore rate limiting

Common Pitfalls

IssueSolution
JWT in localStorageUse httpOnly cookies
No token expirationSet short TTL + refresh tokens
Weak passwordsEnforce strong policy with zod
No rate limitingUse express-rate-limit + Redis
Client-only authAlways validate server-side