| name | secure-auth-patterns |
| description | Secure Auth Patterns |
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 identity | Check permissions |
| Issue credentials | Enforce policies |
| Login/logout | Access control |
Authentication Strategies
| Strategy | Pros | Cons | Best For |
|---|
| Session | Simple, secure | Stateful, scaling | Traditional web apps |
| JWT | Stateless, scalable | Token size, revocation | APIs, microservices |
| OAuth2/OIDC | Delegated, SSO | Complex setup | Social 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' }
);
const refreshToken = jwt.sign(
{ userId: user.id },
process.env.JWT_REFRESH_SECRET!,
{ expiresIn: '7d' }
);
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 {
const payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET!);
const storedToken = await db.refreshTokens.findOne({
token: await hash(refreshToken),
expiresAt: { $gt: new Date() }
});
if (!storedToken) {
return res.status(403).json({ error: 'Token revoked' });
}
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 });
} {
res.().({ : });
}
});
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',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000,
sameSite: 'strict'
}
}));
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))) {
res.().({ : });
}
req.. = user.;
req.. = user.;
res.({ : { : user., : user. } });
});
app.(, {
req..( {
res.();
res.({ : });
});
});
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) => {
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);
}));
app.get('/api/auth/google',
passport.authenticate('google', { scope: ['profile', ] }));
app.(,
passport.(, { : }),
{
tokens = (req.);
res.();
});
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: ) => {
(!req.) {
res.().({ : });
}
(!roles.( (req.., role))) {
res.().({ : });
}
();
};
}
app.(,
authenticate,
(.),
(req, res) => {
db..(req..);
res.({ : });
}
);
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) res.().({ : });
hasAll = permissions.(
rolePermissions[req..]?.(p)
);
(!hasAll) res.().({ : });
();
};
}
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' });
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' });
}
();
};
}
app.(, authenticate, (), 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);
}
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
| Issue | Solution |
|---|
| JWT in localStorage | Use httpOnly cookies |
| No token expiration | Set short TTL + refresh tokens |
| Weak passwords | Enforce strong policy with zod |
| No rate limiting | Use express-rate-limit + Redis |
| Client-only auth | Always validate server-side |