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