Authentication and Authorization Patterns
Reference for implementing authentication in web, mobile, and API applications.
1. JWT (JSON Web Tokens)
Structure
Three Base64URL-encoded parts separated by dots: header.payload.signature.
- Header -- algorithm (
alg) and token type (typ).
- Payload -- claims:
sub, iat, exp, iss, aud, plus custom claims.
- Signature -- HMAC-SHA256 or RSA/ECDSA over header and payload.
Signing
- Symmetric (HS256) -- shared secret; simple but the secret must live on every verifier.
- Asymmetric (RS256, ES256) -- private key signs, public key verifies. Publish via JWKS endpoint.
Access + Refresh Token Flow
- Client authenticates. Server returns a short-lived access token (5-15 min) and a long-lived refresh token.
- Client sends
Authorization: Bearer <access_token> on each request.
- On expiry, client posts the refresh token to a dedicated endpoint to get a new access token.
- Server optionally rotates the refresh token on each use. If revoked/expired, user re-authenticates.
JWT Example (Node.js)
const jwt = require("jsonwebtoken");
const accessToken = jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: "15m" });
const refreshToken = jwt.sign({ sub: user.id }, process.env.REFRESH_SECRET, { expiresIn: "7d" });
function verifyToken(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith("Bearer ")) return res.sendStatus(401);
try { req.user = jwt.verify(header.slice(7), process.env.JWT_SECRET); next(); }
catch { return res.sendStatus(401); }
}
JWT Pitfalls
- JWTs are encoded, not encrypted -- never put secrets in claims.
- Always validate
exp, iss, and aud on the server.
- Enforce the expected algorithm to prevent
alg: none or algorithm-switching attacks.
2. OAuth 2.0 Flows
Authorization Code Flow
For server-side apps. Client gets an authorization code via redirect and exchanges it for tokens server-side.
- Redirect to
GET /authorize?response_type=code&client_id=...&redirect_uri=...&scope=...&state=...
- User authenticates and consents. Auth server redirects with
?code=...&state=...
- Server exchanges the code:
POST /token with grant_type=authorization_code, code, client_id, client_secret.
Authorization Code with PKCE
Required for public clients (SPAs, mobile) that cannot store a client secret.
- Generate random
code_verifier, derive code_challenge = BASE64URL(SHA256(code_verifier)).
- Send
code_challenge and code_challenge_method=S256 in the authorize request.
- Send
code_verifier in the token exchange. Server verifies the hash matches.
Client Credentials Flow
Machine-to-machine, no user. POST /token with grant_type=client_credentials, client_id, client_secret, scope. Returns an access token directly.
Device Authorization Flow
For input-constrained devices (smart TVs, CLI tools). Device gets a user code and verification URL, user authenticates on another device, device polls until complete.
OAuth Example (Python)
import requests
resp = requests.post("https://auth.example.com/token", data={
"grant_type": "authorization_code", "code": auth_code,
"redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET,
})
access_token = resp.json()["access_token"]
data = requests.get("https://api.example.com/data",
headers={"Authorization": f"Bearer {access_token}"}).json()
3. Session-Based Authentication
- User submits credentials. Server creates a session, stores it (memory/Redis/database), returns a session ID cookie.
- Browser sends the cookie automatically. Server looks up the session to identify the user.
Cookie Configuration
Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600
- HttpOnly -- not accessible to JavaScript. Secure -- HTTPS only. SameSite -- mitigates CSRF.
Session Stores
| Store |
Pros |
Cons |
| Memory |
Zero setup |
Lost on restart, single-process |
| Redis |
Fast, TTL support, clustered |
Extra infrastructure |
| Database |
Durable, queryable |
Slower, needs cleanup job |
Sliding vs. Absolute Expiration
- Absolute -- expires at a fixed time. Sliding -- resets on each request. Combine both: slide 30 min with an 8-hour hard cap.
4. API Key Authentication
- Generate with a CSPRNG (
crypto.randomBytes(32), secrets.token_urlsafe(32)). Prefix for identification: sk_live_....
- Store hashed (SHA-256) in the database. Show the raw key to the user once at creation.
- Rotate by allowing a new key before revoking the old one; support an overlap period.
- Best for server-to-server calls and usage tracking. Not suitable as sole auth for user-facing apps.
5. Basic Authentication
Authorization: Basic base64(username:password)
Acceptable: internal services behind VPN over TLS, development environments, webhook shared secrets.
Not acceptable: public-facing APIs, anything without HTTPS, anywhere token-based auth is feasible. Always pair with rate limiting.
6. Multi-Factor Authentication
TOTP (Time-Based One-Time Passwords)
- Shared secret provisioned via QR code (
otpauth:// URI). Client generates a 6-digit code every 30 seconds.
- Server accepts current step +/- 1 for clock skew. Libraries:
pyotp, speakeasy.
WebAuthn / Passkeys
- Phishing-resistant: credential is bound to the origin by the browser.
- Registration: server sends challenge, authenticator creates key pair, public key stored server-side.
- Authentication: server sends challenge, authenticator signs it, server verifies.
- Passkeys sync across devices via platform credential managers.
Guidance
- Offer hashed backup codes. Store MFA secrets encrypted at rest.
- Do not reveal MFA status during login -- check password first, then prompt for second factor.
7. Single Sign-On (SSO)
SAML 2.0
- XML-based, common in enterprise. SP redirects to IdP, IdP posts a signed assertion to the SP's ACS URL.
- Validate signature, audience, timestamps, and InResponseTo on every assertion.
OpenID Connect (OIDC)
- Identity layer on OAuth 2.0. Authorization Code flow returns an
id_token (JWT) with user claims (sub, email, name).
- Discovery at
/.well-known/openid-configuration. Verify id_token via JWKS; validate iss, aud, exp, nonce.
Choosing
- OIDC: simpler, JSON-based, better for modern apps. SAML: entrenched in enterprise; support when customers require it.
8. Password Hashing
| Algorithm |
Notes |
| bcrypt |
Widely supported, cost factor 12+ recommended |
| Argon2id |
Password Hashing Competition winner, memory-hard |
| scrypt |
Memory-hard, good where Argon2 is unavailable |
Never use MD5, SHA-1, or SHA-256 alone -- these are fast hashes, trivially brute-forced. Never use unsalted hashes.
import bcrypt
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
is_valid = bcrypt.checkpw(password.encode(), hashed)
9. Token Storage in the Browser
| Method |
XSS Risk |
CSRF Risk |
Notes |
| httpOnly cookie |
Low |
Medium |
Not accessible to JS; pair with SameSite + CSRF |
| localStorage |
High |
None |
Readable by any script on the page |
| sessionStorage |
High |
None |
Clears on tab close |
| In-memory (JS) |
Low |
None |
Lost on refresh; use silent refresh flow |
Recommended: refresh tokens in httpOnly Secure SameSite cookies; access tokens in memory; silent refresh on page load.
10. CORS and Authentication Headers
- Use specific origins (not
*) with Access-Control-Allow-Credentials: true.
- Include
Authorization in Access-Control-Allow-Headers.
- Keep allowed origins in server config, not hardcoded.
11. Rate Limiting for Auth Endpoints
- Strict limits on
/login, /token, /register, /password-reset.
- Rate limit by both IP and account to counter credential stuffing.
- Return
429 Too Many Requests with Retry-After. Use exponential backoff lockouts.
- Separate auth rate limits from general API limits.
12. Common Vulnerabilities
- Token leakage -- tokens in query strings get logged and leaked via referrer headers. Send in headers or POST bodies. Mask tokens in logs.
- Session fixation -- attacker sets a session ID before login. Mitigate by regenerating the session ID on authentication.
- CSRF -- browser sends cookies automatically. Mitigate with SameSite cookies, anti-CSRF tokens, and Origin/Referer verification.
- Credential stuffing -- breached password lists. Mitigate with rate limiting, MFA, and breach-detection APIs.
- JWT algorithm confusion -- enforce the expected algorithm server-side; reject
alg: none.
- Open redirects in OAuth -- validate
redirect_uri matches a pre-registered value exactly.
13. Auth Middleware Patterns
Express (Node.js)
function requireAuth(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) return res.status(401).json({ error: "Missing token" });
try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); }
catch { return res.status(401).json({ error: "Invalid token" }); }
}
function requireRole(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) return res.status(403).json({ error: "Forbidden" });
next();
};
}
app.get("/admin", requireAuth, requireRole("admin"), adminHandler);
FastAPI (Python)
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def get_current_user(creds: HTTPAuthorizationCredentials = Depends(security)):
try:
payload = jwt.decode(creds.credentials, SECRET_KEY, algorithms=["HS256"])
except jwt.InvalidTokenError:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
user = await get_user(payload["sub"])
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
return user
@app.get("/profile")
async def profile(user=Depends(get_current_user)):
return {"id": user.id, "email": user.email}
Next.js (Middleware)
import { NextRequest, NextResponse } from "next/server";
import { jwtVerify } from "jose";
const SECRET = new TextEncoder().encode(process.env.JWT_SECRET);
export async function middleware(request: NextRequest) {
const token = request.cookies.get("access_token")?.value;
if (!token) return NextResponse.redirect(new URL("/login", request.url));
try { await jwtVerify(token, SECRET); return NextResponse.next(); }
catch { return NextResponse.redirect(new URL("/login", request.url)); }
}
export const config = { matcher: ["/dashboard/:path*", "/api/protected/:path*"] };
References
- RFC 6749 -- OAuth 2.0 Authorization Framework
- RFC 7519 -- JSON Web Token
- RFC 7636 -- Proof Key for Code Exchange (PKCE)
- RFC 8628 -- Device Authorization Grant
- OpenID Connect Core 1.0
- OWASP Authentication Cheat Sheet
- OWASP Session Management Cheat Sheet
- WebAuthn (W3C)
- NIST SP 800-63B -- Digital Identity Guidelines
1---2name: authentication-patterns3description: Comprehensive authentication and authorization implementation guide. Use when the user asks about JWT tokens, OAuth 2.0 flows, session management, API key auth, SSO setup, SAML, OIDC, password hashing, multi-factor authentication, CSRF protection, token storage, CORS headers, rate limiting auth endpoints, bearer tokens, refresh tokens, OAuth scopes, identity providers, user permissions, role-based access control, attribute-based access control, or any authentication and authorization architecture, implementation, or security patterns.4---56# Authentication and Authorization Patterns78Reference for implementing authentication in web, mobile, and API applications.910---1112## 1. JWT (JSON Web Tokens)1314### Structure1516Three Base64URL-encoded parts separated by dots: `header.payload.signature`.1718- **Header** -- algorithm (`alg`) and token type (`typ`).19- **Payload** -- claims: `sub`, `iat`, `exp`, `iss`, `aud`, plus custom claims.20- **Signature** -- HMAC-SHA256 or RSA/ECDSA over header and payload.2122### Signing2324- **Symmetric (HS256)** -- shared secret; simple but the secret must live on every verifier.25- **Asymmetric (RS256, ES256)** -- private key signs, public key verifies. Publish via JWKS endpoint.2627### Access + Refresh Token Flow28291. Client authenticates. Server returns a short-lived access token (5-15 min) and a long-lived refresh token.302. Client sends `Authorization: Bearer <access_token>` on each request.313. On expiry, client posts the refresh token to a dedicated endpoint to get a new access token.324. Server optionally rotates the refresh token on each use. If revoked/expired, user re-authenticates.3334### JWT Example (Node.js)3536```js37const jwt = require("jsonwebtoken");38const accessToken = jwt.sign({ sub: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: "15m" });39const refreshToken = jwt.sign({ sub: user.id }, process.env.REFRESH_SECRET, { expiresIn: "7d" });4041function verifyToken(req, res, next) {42 const header = req.headers.authorization;43 if (!header?.startsWith("Bearer ")) return res.sendStatus(401);44 try { req.user = jwt.verify(header.slice(7), process.env.JWT_SECRET); next(); }45 catch { return res.sendStatus(401); }46}47```4849### JWT Pitfalls5051- JWTs are encoded, not encrypted -- never put secrets in claims.52- Always validate `exp`, `iss`, and `aud` on the server.53- Enforce the expected algorithm to prevent `alg: none` or algorithm-switching attacks.5455---5657## 2. OAuth 2.0 Flows5859### Authorization Code Flow6061For server-side apps. Client gets an authorization code via redirect and exchanges it for tokens server-side.62631. Redirect to `GET /authorize?response_type=code&client_id=...&redirect_uri=...&scope=...&state=...`642. User authenticates and consents. Auth server redirects with `?code=...&state=...`653. Server exchanges the code: `POST /token` with `grant_type=authorization_code`, code, client_id, client_secret.6667### Authorization Code with PKCE6869Required for public clients (SPAs, mobile) that cannot store a client secret.7071- Generate random `code_verifier`, derive `code_challenge = BASE64URL(SHA256(code_verifier))`.72- Send `code_challenge` and `code_challenge_method=S256` in the authorize request.73- Send `code_verifier` in the token exchange. Server verifies the hash matches.7475### Client Credentials Flow7677Machine-to-machine, no user. `POST /token` with `grant_type=client_credentials`, client_id, client_secret, scope. Returns an access token directly.7879### Device Authorization Flow8081For input-constrained devices (smart TVs, CLI tools). Device gets a user code and verification URL, user authenticates on another device, device polls until complete.8283### OAuth Example (Python)8485```python86import requests87resp = requests.post("https://auth.example.com/token", data={88 "grant_type": "authorization_code", "code": auth_code,89 "redirect_uri": REDIRECT_URI, "client_id": CLIENT_ID, "client_secret": CLIENT_SECRET,90})91access_token = resp.json()["access_token"]92data = requests.get("https://api.example.com/data",93 headers={"Authorization": f"Bearer {access_token}"}).json()94```9596---9798## 3. Session-Based Authentication991001. User submits credentials. Server creates a session, stores it (memory/Redis/database), returns a session ID cookie.1012. Browser sends the cookie automatically. Server looks up the session to identify the user.102103### Cookie Configuration104105```106Set-Cookie: sid=abc123; HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=3600107```108109- **HttpOnly** -- not accessible to JavaScript. **Secure** -- HTTPS only. **SameSite** -- mitigates CSRF.110111### Session Stores112113| Store | Pros | Cons |114|----------|------------------------------|---------------------------------|115| Memory | Zero setup | Lost on restart, single-process |116| Redis | Fast, TTL support, clustered | Extra infrastructure |117| Database | Durable, queryable | Slower, needs cleanup job |118119### Sliding vs. Absolute Expiration120121- **Absolute** -- expires at a fixed time. **Sliding** -- resets on each request. Combine both: slide 30 min with an 8-hour hard cap.122123---124125## 4. API Key Authentication126127- **Generate** with a CSPRNG (`crypto.randomBytes(32)`, `secrets.token_urlsafe(32)`). Prefix for identification: `sk_live_...`.128- **Store** hashed (SHA-256) in the database. Show the raw key to the user once at creation.129- **Rotate** by allowing a new key before revoking the old one; support an overlap period.130- Best for server-to-server calls and usage tracking. Not suitable as sole auth for user-facing apps.131132---133134## 5. Basic Authentication135136`Authorization: Basic base64(username:password)`137138**Acceptable**: internal services behind VPN over TLS, development environments, webhook shared secrets.139**Not acceptable**: public-facing APIs, anything without HTTPS, anywhere token-based auth is feasible. Always pair with rate limiting.140141---142143## 6. Multi-Factor Authentication144145### TOTP (Time-Based One-Time Passwords)146147- Shared secret provisioned via QR code (`otpauth://` URI). Client generates a 6-digit code every 30 seconds.148- Server accepts current step +/- 1 for clock skew. Libraries: `pyotp`, `speakeasy`.149150### WebAuthn / Passkeys151152- Phishing-resistant: credential is bound to the origin by the browser.153- Registration: server sends challenge, authenticator creates key pair, public key stored server-side.154- Authentication: server sends challenge, authenticator signs it, server verifies.155- Passkeys sync across devices via platform credential managers.156157### Guidance158159- Offer hashed backup codes. Store MFA secrets encrypted at rest.160- Do not reveal MFA status during login -- check password first, then prompt for second factor.161162---163164## 7. Single Sign-On (SSO)165166### SAML 2.0167168- XML-based, common in enterprise. SP redirects to IdP, IdP posts a signed assertion to the SP's ACS URL.169- Validate signature, audience, timestamps, and InResponseTo on every assertion.170171### OpenID Connect (OIDC)172173- Identity layer on OAuth 2.0. Authorization Code flow returns an `id_token` (JWT) with user claims (`sub`, `email`, `name`).174- Discovery at `/.well-known/openid-configuration`. Verify `id_token` via JWKS; validate `iss`, `aud`, `exp`, `nonce`.175176### Choosing177178- OIDC: simpler, JSON-based, better for modern apps. SAML: entrenched in enterprise; support when customers require it.179180---181182## 8. Password Hashing183184| Algorithm | Notes |185|-----------|----------------------------------------------------------|186| bcrypt | Widely supported, cost factor 12+ recommended |187| Argon2id | Password Hashing Competition winner, memory-hard |188| scrypt | Memory-hard, good where Argon2 is unavailable |189190**Never use** MD5, SHA-1, or SHA-256 alone -- these are fast hashes, trivially brute-forced. Never use unsalted hashes.191192```python193import bcrypt194hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))195is_valid = bcrypt.checkpw(password.encode(), hashed)196```197198---199200## 9. Token Storage in the Browser201202| Method | XSS Risk | CSRF Risk | Notes |203|-----------------|----------|-----------|-------------------------------------------------|204| httpOnly cookie | Low | Medium | Not accessible to JS; pair with SameSite + CSRF |205| localStorage | High | None | Readable by any script on the page |206| sessionStorage | High | None | Clears on tab close |207| In-memory (JS) | Low | None | Lost on refresh; use silent refresh flow |208209**Recommended**: refresh tokens in httpOnly Secure SameSite cookies; access tokens in memory; silent refresh on page load.210211---212213## 10. CORS and Authentication Headers214215- Use specific origins (not `*`) with `Access-Control-Allow-Credentials: true`.216- Include `Authorization` in `Access-Control-Allow-Headers`.217- Keep allowed origins in server config, not hardcoded.218219---220221## 11. Rate Limiting for Auth Endpoints222223- Strict limits on `/login`, `/token`, `/register`, `/password-reset`.224- Rate limit by both IP and account to counter credential stuffing.225- Return `429 Too Many Requests` with `Retry-After`. Use exponential backoff lockouts.226- Separate auth rate limits from general API limits.227228---229230## 12. Common Vulnerabilities231232- **Token leakage** -- tokens in query strings get logged and leaked via referrer headers. Send in headers or POST bodies. Mask tokens in logs.233- **Session fixation** -- attacker sets a session ID before login. Mitigate by regenerating the session ID on authentication.234- **CSRF** -- browser sends cookies automatically. Mitigate with SameSite cookies, anti-CSRF tokens, and Origin/Referer verification.235- **Credential stuffing** -- breached password lists. Mitigate with rate limiting, MFA, and breach-detection APIs.236- **JWT algorithm confusion** -- enforce the expected algorithm server-side; reject `alg: none`.237- **Open redirects in OAuth** -- validate `redirect_uri` matches a pre-registered value exactly.238239---240241## 13. Auth Middleware Patterns242243### Express (Node.js)244245```js246function requireAuth(req, res, next) {247 const token = req.headers.authorization?.split(" ")[1];248 if (!token) return res.status(401).json({ error: "Missing token" });249 try { req.user = jwt.verify(token, process.env.JWT_SECRET); next(); }250 catch { return res.status(401).json({ error: "Invalid token" }); }251}252function requireRole(...roles) {253 return (req, res, next) => {254 if (!roles.includes(req.user.role)) return res.status(403).json({ error: "Forbidden" });255 next();256 };257}258app.get("/admin", requireAuth, requireRole("admin"), adminHandler);259```260261### FastAPI (Python)262263```python264from fastapi import Depends, HTTPException, status265from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials266security = HTTPBearer()267268async def get_current_user(creds: HTTPAuthorizationCredentials = Depends(security)):269 try:270 payload = jwt.decode(creds.credentials, SECRET_KEY, algorithms=["HS256"])271 except jwt.InvalidTokenError:272 raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)273 user = await get_user(payload["sub"])274 if not user:275 raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)276 return user277278@app.get("/profile")279async def profile(user=Depends(get_current_user)):280 return {"id": user.id, "email": user.email}281```282283### Next.js (Middleware)284285```ts286import { NextRequest, NextResponse } from "next/server";287import { jwtVerify } from "jose";288const SECRET = new TextEncoder().encode(process.env.JWT_SECRET);289290export async function middleware(request: NextRequest) {291 const token = request.cookies.get("access_token")?.value;292 if (!token) return NextResponse.redirect(new URL("/login", request.url));293 try { await jwtVerify(token, SECRET); return NextResponse.next(); }294 catch { return NextResponse.redirect(new URL("/login", request.url)); }295}296export const config = { matcher: ["/dashboard/:path*", "/api/protected/:path*"] };297```298299---300301## References302303- RFC 6749 -- OAuth 2.0 Authorization Framework304- RFC 7519 -- JSON Web Token305- RFC 7636 -- Proof Key for Code Exchange (PKCE)306- RFC 8628 -- Device Authorization Grant307- OpenID Connect Core 1.0308- OWASP Authentication Cheat Sheet309- OWASP Session Management Cheat Sheet310- WebAuthn (W3C)311- NIST SP 800-63B -- Digital Identity Guidelines