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 |
Installation
OpenClaw / Moltbot / Clawbot
npx clawhub@latest install auth-patterns
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 |
Security Headers
| 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 |
1---2name: auth-patterns-43description: Authentication and authorization patterns — JWT, OAuth 2.0, sessions, RBAC/ABAC, password security, MFA, and vulnerability prevention. Use when implementing login flows, protecting routes, managing tokens, or auditing auth security.4---5
6# Auth Patterns — Authentication & Authorization
7
8> **SECURITY-CRITICAL SKILL** — Auth is the front door. Get it wrong and nothing else matters.
9
10## Authentication Methods
11
12| Method | How It Works | Best For |
13|--------|-------------|----------|
14| **JWT** | Signed token sent with each request | SPAs, microservices, mobile APIs |
15| **Session-based** | Server stores session, client holds cookie | Traditional web apps, SSR |
16| **OAuth 2.0** | Delegated auth via authorization server | "Login with Google/GitHub", API access |
17| **API Keys** | Static key sent in header | Internal services, public APIs |
18| **Magic Links** | One-time login link via email | Low-friction onboarding, B2C |
19| **Passkeys/WebAuthn** | Hardware/biometric challenge-response | High-security apps, passwordless |
20
21
22## Installation
23
24### OpenClaw / Moltbot / Clawbot
25
26```bash
27npx clawhub@latest install auth-patterns
28```
29
30
31---
32
33## JWT Patterns
34
35### Dual-Token Strategy
36
37Short-lived access token + long-lived refresh token:
38
39```
40Client → POST /auth/login → Server
41Client ← { access_token, refresh_token }
42
43Client → GET /api/data (Authorization: Bearer <access>) → Server
44Client ← 401 Expired
45
46Client → POST /auth/refresh { refresh_token } → Server
47Client ← { new_access_token, rotated_refresh_token }
48```
49
50### Token Structure
51
52```json
53{
54 "header": { "alg": "RS256", "typ": "JWT", "kid": "key-2024-01" },
55 "payload": {
56 "sub": "user_abc123",
57 "iss": "https://auth.example.com",
58 "aud": "https://api.example.com",
59 "exp": 1700000900,
60 "iat": 1700000000,
61 "jti": "unique-token-id",
62 "roles": ["user"],
63 "scope": "read:profile write:profile"
64 }
65}
66```
67
68### Signing Algorithms
69
70| Algorithm | Type | When to Use |
71|-----------|------|-------------|
72| **RS256** | Asymmetric (RSA) | Microservices — only auth server holds private key |
73| **ES256** | Asymmetric (ECDSA) | Same as RS256, smaller keys and signatures |
74| **HS256** | Symmetric | Single-server apps — all verifiers share secret |
75
76Prefer RS256/ES256 in distributed systems.
77
78### Token Storage
79
80| Storage | XSS Safe | CSRF Safe | Recommendation |
81|---------|----------|-----------|----------------|
82| **httpOnly cookie** | Yes | No (add CSRF token) | **Best for web apps** |
83| **localStorage** | No | Yes | Avoid — XSS exposes tokens |
84| **In-memory** | Yes | Yes | Good for SPAs, lost on refresh |
85
86```
87Set-Cookie: access_token=eyJ...; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=900
88```
89
90### Expiration Strategy
91
92| Token | Lifetime | Rotation |
93|-------|----------|----------|
94| **Access token** | 5–15 minutes | Issued on refresh |
95| **Refresh token** | 7–30 days | Rotate on every use |
96| **ID token** | Match access token | Not refreshed |
97
98---
99
100## OAuth 2.0 Flows
101
102| Flow | Client Type | When to Use |
103|------|-------------|-------------|
104| **Authorization Code + PKCE** | Public (SPA, mobile) | **Default for all public clients** |
105| **Authorization Code** | Confidential (server) | Server-rendered web apps with backend |
106| **Client Credentials** | Machine-to-machine | Service-to-service, cron jobs |
107| **Device Code** | Input-constrained | Smart TVs, IoT, CLI on headless servers |
108
109> **Implicit flow is deprecated.** Always use Authorization Code + PKCE for public clients.
110
111### PKCE Flow
112
113```
1141. Client generates code_verifier (random 43-128 chars)
1152. Client computes code_challenge = BASE64URL(SHA256(code_verifier))
1163. Redirect to /authorize?code_challenge=...&code_challenge_method=S256
1174. User authenticates, server redirects back with authorization code
1185. Client exchanges code + code_verifier for tokens at /token
1196. Server verifies SHA256(code_verifier) == code_challenge
120```
121
122---
123
124## Session Management
125
126### Server-Side Sessions
127
128```
129Client Cookie: session_id=a1b2c3d4 (opaque, random, no user data)
130Server Store: { "a1b2c3d4": { userId: 123, roles: ["admin"], expiresAt: ... } }
131```
132
133| Store | Speed | When to Use |
134|-------|-------|-------------|
135| **Redis** | Fast | Production default — TTL support, horizontal scaling |
136| **PostgreSQL** | Moderate | When Redis is overkill, need audit trail |
137| **In-memory** | Fastest | Development only |
138
139### Session Security
140
141| Threat | Prevention |
142|--------|------------|
143| Session fixation | Regenerate session ID after login |
144| Session hijacking | httpOnly + Secure cookies, bind to IP/user-agent |
145| CSRF | SameSite cookies + CSRF tokens |
146| Idle timeout | Expire after 15–30 min inactivity |
147| Absolute timeout | Force re-auth after 8–24 hours |
148
149---
150
151## Authorization Patterns
152
153| Pattern | Granularity | When to Use |
154|---------|-------------|-------------|
155| **RBAC** | Coarse (admin, editor, viewer) | Most apps — simple role hierarchy |
156| **ABAC** | Fine (attributes: dept, time, location) | Enterprise — context-dependent access |
157| **Permission-based** | Medium (post:create, user:delete) | APIs — decouple permissions from roles |
158| **Policy-based (OPA/Cedar)** | Fine | Microservices — externalized, auditable rules |
159| **ReBAC** | Fine (owner, member, shared-with) | Social apps, Google Drive-style sharing |
160
161### RBAC Implementation
162
163```typescript
164const ROLE_PERMISSIONS: Record<string, string[]> = {
165 admin: ["user:read", "user:write", "user:delete", "post:read", "post:write", "post:delete"],
166 editor: ["user:read", "post:read", "post:write"],
167 viewer: ["user:read", "post:read"],
168};
169
170function requirePermission(permission: string) {
171 return (req: Request, res: Response, next: NextFunction) => {
172 const permissions = ROLE_PERMISSIONS[req.user.role] ?? [];
173 if (!permissions.includes(permission)) {
174 return res.status(403).json({ error: "Forbidden" });
175 }
176 next();
177 };
178}
179
180app.delete("/api/users/:id", requirePermission("user:delete"), deleteUser);
181```
182
183---
184
185## Password Security
186
187| Algorithm | Recommended | Memory-Hard | Notes |
188|-----------|------------|-------------|-------|
189| **Argon2id** | **First choice** | Yes | Resists GPU/ASIC attacks |
190| **bcrypt** | Yes | No | Battle-tested, 72-byte limit |
191| **scrypt** | Yes | Yes | Good alternative |
192| **PBKDF2** | Acceptable | No | NIST approved, weaker vs GPU |
193| **SHA-256/MD5** | **Never** | No | Not password hashing |
194
195**NIST 800-63B:** Favor length (12+ chars) over complexity rules. Check against breached password lists. Don't force periodic rotation unless breach suspected.
196
197---
198
199## Multi-Factor Authentication
200
201| Factor | Security | Notes |
202|--------|----------|-------|
203| **TOTP (Authenticator app)** | High | Offline-capable, Google Authenticator / Authy |
204| **WebAuthn/Passkeys** | Highest | Phishing-resistant, hardware-backed |
205| **SMS OTP** | Medium | Vulnerable to SIM swap — avoid for high-security |
206| **Hardware keys (FIDO2)** | Highest | YubiKey — best for admin accounts |
207| **Backup codes** | Low (fallback) | One-time use, generate 10, store hashed |
208
209---
210
211## Security Headers
212
213| Header | Value |
214|--------|-------|
215| **Strict-Transport-Security** | `max-age=63072000; includeSubDomains; preload` |
216| **Content-Security-Policy** | Restrict script sources, no inline scripts |
217| **X-Content-Type-Options** | `nosniff` |
218| **X-Frame-Options** | `DENY` |
219| **Referrer-Policy** | `strict-origin-when-cross-origin` |
220| **CORS** | Whitelist specific origins, never `*` with credentials |
221
222---
223
224## Common Vulnerabilities
225
226| # | Vulnerability | Prevention |
227|---|--------------|------------|
228| 1 | Broken authentication | MFA, strong password policy, breach detection |
229| 2 | Session fixation | Regenerate session ID on login |
230| 3 | JWT `alg:none` attack | Reject `none`, validate `alg` against allowlist |
231| 4 | JWT secret brute force | Use RS256/ES256, strong secrets (256+ bits) |
232| 5 | CSRF | SameSite cookies, CSRF tokens |
233| 6 | Credential stuffing | Rate limiting, breached password check, MFA |
234| 7 | Insecure password storage | Argon2id/bcrypt, never encrypt (hash instead) |
235| 8 | Insecure password reset | Signed time-limited tokens, invalidate after use |
236| 9 | Open redirect | Validate redirect URIs against allowlist |
237| 10 | Token leakage in URL | Send tokens in headers or httpOnly cookies only |
238| 11 | Privilege escalation | Server-side role checks on every request |
239| 12 | OAuth redirect_uri mismatch | Exact match redirect URI validation, no wildcards |
240| 13 | Timing attacks | Constant-time comparison for secrets |
241
242---
243
244## NEVER Do
245
246| # | Rule | Why |
247|---|------|-----|
248| 1 | **NEVER store passwords in plaintext or reversible encryption** | One breach exposes every user |
249| 2 | **NEVER put tokens in URLs or query parameters** | Logged by servers, proxies, referrer headers |
250| 3 | **NEVER use `alg: none` or allow algorithm switching in JWTs** | Attacker forges tokens |
251| 4 | **NEVER trust client-side role/permission claims** | Users can modify any client-side value |
252| 5 | **NEVER use MD5, SHA-1, or plain SHA-256 for password hashing** | No salt, no work factor — cracked in seconds |
253| 6 | **NEVER skip HTTPS in production** | Tokens and credentials sent in cleartext |
254| 7 | **NEVER log tokens, passwords, or secrets** | Logs are broadly accessible and retained |
255| 8 | **NEVER use long-lived tokens without rotation** | A single leak grants indefinite access |
256| 9 | **NEVER implement your own crypto** | Use established libraries — jose, bcrypt, passport |
257| 10 | **NEVER return different errors for "user not found" vs "wrong password"** | Enables user enumeration |