Authentication Patterns with Claude Code: JWT, Sessions, and OAuth Done Right
Source: Dev.to
CLAUDE.md for Authentication
## Authentication Rules
### JWT
- Use RS256 (asymmetric) for production, HS256 only for development
- Token expiry: access token 15 min, refresh token 7 days
- Store access token in memory (not localStorage)
- Store refresh token in httpOnly cookie
- Never put sensitive data in JWT payload (only userId, role)
### Session
- Use express-session with Redis store (not in‑memory)
- Session cookie: httpOnly, secure (production), sameSite: strict
- Regenerate session ID on login
### Passwords
- Hash with bcrypt, cost factor 12
- Never log or transmit passwords in plain text
- Minimum entropy: 8 chars, require uppercase + number
### OAuth
- Validate state parameter (CSRF protection)
- Use PKCE for public clients
- Verify token signatures from provider
- Don't trust email as unique identifier (use provider's user ID)
### Protected Routes
- All authenticated routes use `src/middleware/auth.ts`
- Role‑based access: use `src/middleware/authorize.ts`
- Rate limit: login (10 req/min), register (5 req/min)
JWT Implementation
// Generate a JWT authentication service
// - Login: validate credentials, issue access + refresh tokens
// - Refresh: validate refresh token, issue new access token
// - Logout: invalidate refresh token (stored in Redis)
// - Middleware: verify access token on protected routes
//
// Follow CLAUDE.md auth rules:
// - RS256 algorithm
// - Access token: 15 min expiry
// - Refresh token: 7 days, stored in Redis
// - No sensitive data in payload
Password Hashing
// Password utility module (src/lib/password.ts)
// Requirements:
// - Hash with bcrypt, cost factor 12
// - Compare plain text with hash (timing‑safe)
// - Never export the raw hash or log it
// - Return boolean only from compare function
Protected Route Middleware
// Authentication middleware for Express (src/middleware/auth.ts)
// Requirements:
// - Extract JWT from `Authorization: Bearer` header
// - Verify signature with public key from `src/config/keys.ts`
// - Reject expired tokens with 401 + specific error code
// - Attach decoded user to `req.user`
// - Handle: missing token (401), invalid signature (401), expired (401)
Role‑Based Access Control
// RBAC middleware (src/middleware/authorize.ts)
// Roles: admin, user, viewer (decreasing permissions)
// - Admin can access all
// - User can access user + viewer
// - Viewer only viewer
//
// Usage example:
// router.get('/admin', auth, authorize('admin'), handler)
OAuth Integration Security
// Google OAuth callback handler (src/routes/auth/google.ts)
// Security requirements:
// - Validate state parameter against session (CSRF protection)
// - Use authorization code flow (not implicit)
// - Exchange code for tokens server‑side
// - Use Google's user ID (not email) as the unique identifier
// - Handle account linking: if email matches existing user, link accounts
Auth Testing
// Tests for the JWT auth middleware
// Test cases:
// - Valid token: passes with user attached to req.user
// - Missing Authorization header: 401
// - Malformed token (not valid JWT): 401
// - Valid signature but expired: 401 with EXPIRED_TOKEN code
// - Valid signature, wrong audience: 401
// - Admin‑only route with user role: 403
//
// Mock the JWT verify function; do not use real keys in tests.
Common Auth Mistakes Claude Code Catches
| Mistake | Caught by |
|---|---|
localStorage.setItem('token', ...) | CLAUDE.md rule about httpOnly cookies |
jwt.sign({...user}) with full user object | Rule about payload content |
| bcrypt cost factor 10 | Rule specifying factor 12 |
| Missing state validation in OAuth | Rule about CSRF protection |
if (user.email === req.body.email) without timing‑safe comparison | Rule about comparison |
Security Pack (¥1,480) includes /secret-scanner and security‑focused /code-review patterns for auth code.
👉