Auth Patterns — Authentication & Authorization
SECURITY-CRITICAL SKILL — Auth is the front door. Get it wrong and nothing else matters.
Authentication Methods
| Method |
How It Works |
Best For |
| JWT |
Signed token sent with each request |
SPAs, microservices, mobile APIs |
| Session-based |
Server stores session, client holds cookie |
Traditional web apps, SSR |
| OAuth 2.0 |
Delegated auth via authorization server |
"Login with Google/GitHub", API access |
| API Keys |
Static key sent in header |
Internal services, public APIs |
| Magic Links |
One-time login link via email |
Low-friction onboarding, B2C |
| Passkeys/WebAuthn |
Hardware/biometric challenge-response |
High-security apps, passwordless |
JWT Patterns
Dual-Token Strategy
Short-lived access token + long-lived refresh token:
Client → POST /auth/login → Server
Client ← { access_token, refresh_token }
Client → GET /api/data (Authorization: Bearer <access>) → Server
Client ← 401 Expired
Client → POST /auth/refresh { refresh_token } → Server
Client ← { new_access_token, rotated_refresh_token }
Token Structure
{
"header": { "alg": "RS256", "typ": "JWT", "kid": "key-2024-01" },
"payload": {
"sub": "user_abc123",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"exp": 1700000900,
"iat": 1700000000,
"jti": "unique-token-id",
"roles": ["user"],
"scope": "read:profile write:profile"
}
}
Signing Algorithms
| Algorithm |
Type |
When to Use |
| RS256 |
Asymmetric (RSA) |
Microservices — only auth server holds private key |
| ES256 |
Asymmetric (ECDSA) |
Same as RS256, smaller keys and signatures |
| HS256 |
Symmetric |
Single-server apps — all verifiers share secret |
Prefer RS256/ES256 in distributed systems.
Token Storage
| Storage |
XSS Safe |
CSRF Safe |
Recommendation |
| httpOnly cookie |
Yes |
No (add CSRF token) |
Best for web apps |
| localStorage |
No |
Yes |
Avoid — XSS exposes tokens |
| In-memory |
Yes |
Yes |
Good for SPAs, lost on refresh |
Set-Cookie: access_token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
Expiration Strategy
| Token |
Lifetime |
Rotation |
| Access token |
5–15 minutes |
Issued on refresh |
| Refresh token |
7–30 days |
Rotate on every use |
| ID token |
Match access token |
Not refreshed |
OAuth 2.0 Flows
| Flow |
Client Type |
When to Use |
| Authorization Code + PKCE |
Public (SPA, mobile) |
Default for all public clients |
| Authorization Code |
Confidential (server) |
Server-rendered web apps with backend |
| Client Credentials |
Machine-to-machine |
Service-to-service, cron jobs |
| Device Code |
Input-constrained |
Smart TVs, IoT, CLI on headless servers |
Implicit flow is deprecated. Always use Authorization Code + PKCE for public clients.
PKCE Flow
1. Client generates code_verifier (random 43-128 chars)
2. Client computes code_challenge = BASE64URL(SHA256(code_verifier))
3. Redirect to /authorize?code_challenge=...&code_challenge_method=S256
4. User authenticates, server redirects back with authorization code
5. Client exchanges code + code_verifier for tokens at /token
6. Server verifies SHA256(code_verifier) == code_challenge
Session Management
Server-Side Sessions
Client Cookie: session_id=a1b2c3d4 (opaque, random, no user data)
Server Store: { "a1b2c3d4": { userId: 123, roles: ["admin"], expiresAt: ... } }
| Store |
Speed |
When to Use |
| Redis |
Fast |
Production default — TTL support, horizontal scaling |
| PostgreSQL |
Moderate |
When Redis is overkill, need audit trail |
| In-memory |
Fastest |
Development only |
Session Security
| Threat |
Prevention |
| Session fixation |
Regenerate session ID after login |
| Session hijacking |
httpOnly + Secure cookies, bind to IP/user-agent |
| CSRF |
SameSite cookies + CSRF tokens |
| Idle timeout |
Expire after 15–30 min inactivity |
| Absolute timeout |
Force re-auth after 8–24 hours |
Authorization Patterns
| Pattern |
Granularity |
When to Use |
| RBAC |
Coarse (admin, editor, viewer) |
Most apps — simple role hierarchy |
| ABAC |
Fine (attributes: dept, time, location) |
Enterprise — context-dependent access |
| Permission-based |
Medium (post:create, user:delete) |
APIs — decouple permissions from roles |
| Policy-based (OPA/Cedar) |
Fine |
Microservices — externalized, auditable rules |
| ReBAC |
Fine (owner, member, shared-with) |
Social apps, Google Drive-style sharing |
RBAC Implementation
const ROLE_PERMISSIONS: Record<string, string[]> = {
admin: ["user:read", "user:write", "user:delete", "post:read", "post:write", "post:delete"],
editor: ["user:read", "post:read", "post:write"],
viewer: ["user:read", "post:read"],
};
function requirePermission(permission: string) {
return (req: Request, res: Response, next: NextFunction) => {
const permissions = ROLE_PERMISSIONS[req.user.role] ?? [];
if (!permissions.includes(permission)) {
return res.status(403).json({ error: "Forbidden" });
}
next();
};
}
app.delete("/api/users/:id", requirePermission("user:delete"), deleteUser);
Password Security
| Algorithm |
Recommended |
Memory-Hard |
Notes |
| Argon2id |
First choice |
Yes |
Resists GPU/ASIC attacks |
| bcrypt |
Yes |
No |
Battle-tested, 72-byte limit |
| scrypt |
Yes |
Yes |
Good alternative |
| PBKDF2 |
Acceptable |
No |
NIST approved, weaker vs GPU |
| SHA-256/MD5 |
Never |
No |
Not password hashing |
NIST 800-63B: Favor length (12+ chars) over complexity rules. Check against breached password lists. Don't force periodic rotation unless breach suspected.
Multi-Factor Authentication
| Factor |
Security |
Notes |
| TOTP (Authenticator app) |
High |
Offline-capable, Google Authenticator / Authy |
| WebAuthn/Passkeys |
Highest |
Phishing-resistant, hardware-backed |
| SMS OTP |
Medium |
Vulnerable to SIM swap — avoid for high-security |
| Hardware keys (FIDO2) |
Highest |
YubiKey — best for admin accounts |
| Backup codes |
Low (fallback) |
One-time use, generate 10, store hashed |
| Header |
Value |
| Strict-Transport-Security |
max-age=63072000; includeSubDomains; preload |
| Content-Security-Policy |
Restrict script sources, no inline scripts |
| X-Content-Type-Options |
nosniff |
| X-Frame-Options |
DENY |
| Referrer-Policy |
strict-origin-when-cross-origin |
| CORS |
Whitelist specific origins, never * with credentials |
Common Vulnerabilities
| # |
Vulnerability |
Prevention |
| 1 |
Broken authentication |
MFA, strong password policy, breach detection |
| 2 |
Session fixation |
Regenerate session ID on login |
| 3 |
JWT alg:none attack |
Reject none, validate alg against allowlist |
| 4 |
JWT secret brute force |
Use RS256/ES256, strong secrets (256+ bits) |
| 5 |
CSRF |
SameSite cookies, CSRF tokens |
| 6 |
Credential stuffing |
Rate limiting, breached password check, MFA |
| 7 |
Insecure password storage |
Argon2id/bcrypt, never encrypt (hash instead) |
| 8 |
Insecure password reset |
Signed time-limited tokens, invalidate after use |
| 9 |
Open redirect |
Validate redirect URIs against allowlist |
| 10 |
Token leakage in URL |
Send tokens in headers or httpOnly cookies only |
| 11 |
Privilege escalation |
Server-side role checks on every request |
| 12 |
OAuth redirect_uri mismatch |
Exact match redirect URI validation, no wildcards |
| 13 |
Timing attacks |
Constant-time comparison for secrets |
NEVER Do
| # |
Rule |
Why |
| 1 |
NEVER store passwords in plaintext or reversible encryption |
One breach exposes every user |
| 2 |
NEVER put tokens in URLs or query parameters |
Logged by servers, proxies, referrer headers |
| 3 |
NEVER use alg: none or allow algorithm switching in JWTs |
Attacker forges tokens |
| 4 |
NEVER trust client-side role/permission claims |
Users can modify any client-side value |
| 5 |
NEVER use MD5, SHA-1, or plain SHA-256 for password hashing |
No salt, no work factor — cracked in seconds |
| 6 |
NEVER skip HTTPS in production |
Tokens and credentials sent in cleartext |
| 7 |
NEVER log tokens, passwords, or secrets |
Logs are broadly accessible and retained |
| 8 |
NEVER use long-lived tokens without rotation |
A single leak grants indefinite access |
| 9 |
NEVER implement your own crypto |
Use established libraries — jose, bcrypt, passport |
| 10 |
NEVER return different errors for "user not found" vs "wrong password" |
Enables user enumeration |