Authentication verifies who users are. Authorization determines what they can access. This guide covers implementation approaches with security best practices from official documentation and OWASP guidelines.
Authentication Methods Overview
| Method | Best For | Complexity |
|---|---|---|
| Session-based | Traditional web apps | Low |
| JWT (stateless) | APIs, microservices | Medium |
| OAuth 2.0 | Third-party login | Medium-High |
| Passwordless | Modern UX | Medium |
| Multi-factor (MFA) | High security | Medium |
Session-Based Authentication
The traditional approach: server stores session data, client holds session ID.
How It Works
- User submits credentials
- Server validates and creates session
- Session ID stored in HTTP-only cookie
- Client sends cookie with each request
- Server looks up session to identify user
Implementation Flow
Client Server
| |
|-- POST /login (credentials) ->|
| |-- Validate credentials
| |-- Create session in store
|<-- Set-Cookie: sessionId -----|
| |
|-- GET /dashboard (cookie) --->|
| |-- Look up session
| |-- Return user data
|<-- 200 OK (user data) --------|
Session Storage Options
| Storage | Pros | Cons |
|---|---|---|
| Memory | Simple, fast | Lost on restart, no scaling |
| Redis | Fast, scalable | Additional infrastructure |
| Database | Persistent, simple | Slower than Redis |
| File | Simple | No horizontal scaling |
Security Requirements
Per OWASP Session Management guidelines:1
Cookie Settings:
HttpOnly- Prevents JavaScript access (XSS protection)Secure- HTTPS onlySameSite=LaxorStrict- CSRF protection- Short expiration - Limit exposure window
Session ID Requirements:
- Minimum 128 bits of entropy
- Cryptographically random
- Regenerate on login (prevent fixation)
Example: Express.js Session
const session = require('express-session');
const RedisStore = require('connect-redis').default;
const redis = require('redis');
const redisClient = redis.createClient();
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JS access
sameSite: 'lax', // CSRF protection
maxAge: 1000 * 60 * 60 * 24 // 24 hours
}
}));
Pros and Cons
Advantages:
- Simple to implement
- Easy session invalidation (delete from store)
- Works with any client
- Proven, well-understood
Disadvantages:
- Server must store sessions
- Requires session store for scaling
- Not ideal for mobile apps or APIs
JSON Web Tokens (JWT)
JWTs are self-contained tokens that encode user information.2
JWT Structure
A JWT has three parts, base64-encoded and separated by dots:
header.payload.signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header: Algorithm and token type
{
"alg": "HS256",
"typ": "JWT"
}
Payload: Claims (user data)
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
Signature: Verifies integrity
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Standard Claims
Per RFC 7519:2
| Claim | Name | Purpose |
|---|---|---|
iss | Issuer | Who created the token |
sub | Subject | Who the token is about (user ID) |
aud | Audience | Who should accept it |
exp | Expiration | When it expires (Unix timestamp) |
iat | Issued At | When it was created |
nbf | Not Before | When it becomes valid |
jti | JWT ID | Unique identifier |
JWT Authentication Flow
Client Server
| |
|-- POST /login (credentials) ->|
| |-- Validate credentials
| |-- Generate JWT
|<-- { token: "eyJ..." } -------|
| |
|-- GET /api (Authorization: Bearer token) -->|
| |-- Verify JWT signature
| |-- Check expiration
| |-- Extract user from payload
|<-- 200 OK (data) -------------|
Where to Store JWTs
| Storage | XSS Safe | CSRF Safe | Notes |
|---|---|---|---|
| HttpOnly Cookie | Yes | Need SameSite | Recommended for web |
| localStorage | No | Yes | Vulnerable to XSS |
| Memory | Yes | Yes | Lost on refresh |
Recommendation: Use HttpOnly cookies for web apps, include CSRF protection.3
Access and Refresh Tokens
Common pattern for better security:
| Token | Lifespan | Purpose |
|---|---|---|
| Access Token | 15-60 minutes | API authentication |
| Refresh Token | Days to weeks | Get new access tokens |
Flow:
- User logs in, receives both tokens
- Access token used for API calls
- When access token expires, use refresh token to get new one
- If refresh token expires, user must log in again
JWT Security Best Practices
From OWASP and JWT best practices:3
- Short expiration - 15 minutes for access tokens
- Strong secrets - Minimum 256 bits for HS256
- Use RS256 for distributed systems - Asymmetric keys
- Validate all claims - Check iss, aud, exp
- Never store sensitive data - Payload is readable
- Use HTTPS only - Tokens are bearer credentials
Token Revocation Challenge
JWTs are stateless—you cannot invalidate them server-side without adding state.
Revocation strategies:
- Short expiration times
- Refresh token rotation
- Token blacklist (requires storage)
- Token versioning in database
Pros and Cons
Advantages:
- Stateless (no server storage)
- Works across services/domains
- Good for APIs and microservices
- Contains user info (no database lookup)
Disadvantages:
- Cannot revoke easily
- Larger than session IDs
- Payload is readable (not encrypted)
- Token size increases with claims
OAuth 2.0 and OpenID Connect
OAuth 2.0 is an authorization framework. OpenID Connect (OIDC) adds authentication on top.4
Key Concepts
OAuth 2.0 Roles:
- Resource Owner: The user
- Client: Your application
- Authorization Server: Issues tokens (Google, GitHub, etc.)
- Resource Server: API with protected resources
OpenID Connect adds:
- ID Token (JWT with user identity)
- UserInfo endpoint
- Standard scopes (openid, profile, email)
Authorization Code Flow
The recommended flow for web applications:5
User Your App Auth Server Resource Server
| | | |
|-- Click Login ->| | |
| |-- Redirect ------->| |
| | (client_id, | |
| | redirect_uri, | |
| | scope, state) | |
|<---------------| | |
| | |
|-- Login at Auth Server ------------>| |
|<-- Redirect to redirect_uri --------| |
| (code, state) | |
|--------------->| | |
| |-- POST /token ---->| |
| | (code, secret) | |
| |<-- access_token ---| |
| | refresh_token | |
| | id_token | |
| | | |
| |-- GET /api (token) |------------------>|
| |<-- user data ------|-------------------|
PKCE (Proof Key for Code Exchange)
Required for public clients (SPAs, mobile apps):6
- Generate random
code_verifier - Create
code_challenge= SHA256(code_verifier) - Include
code_challengein authorization request - Include
code_verifierin token request - Server verifies they match
OAuth Scopes
Scopes limit what access tokens can do:
| Provider | Common Scopes |
|---|---|
| openid, email, profile | |
| GitHub | read:user, user:email, repo |
| Microsoft | openid, profile, email, offline_access |
Implementation Options
Self-hosted:
- Auth.js (NextAuth.js) - Next.js and others
- Passport.js - Node.js middleware
- Lucia - TypeScript auth library
Managed services:
- Auth07
- Clerk
- Supabase Auth
- Firebase Auth
- AWS Cognito
Pros and Cons
Advantages:
- Users don’t create new passwords
- Reduced liability (no password storage)
- Social proof and easy onboarding
- Access to provider APIs
Disadvantages:
- Dependency on third parties
- Complex implementation
- Privacy concerns for users
- Provider outages affect your app
Passwordless Authentication
Skip passwords entirely using email links or passkeys.
Magic Links
Send login link to email:
- User enters email
- Server generates secure token
- Email sent with link containing token
- User clicks link
- Server validates token, creates session
Security requirements:
- Tokens expire quickly (15-60 minutes)
- One-time use
- Cryptographically random (128+ bits)
- Rate limit requests
WebAuthn and Passkeys
WebAuthn enables passwordless authentication using biometrics or security keys.8
How it works:
- Registration: Create public/private key pair
- Login: Sign challenge with private key
- Server verifies signature with public key
Benefits:
- Phishing resistant
- No shared secrets
- Biometric convenience
- Cross-device (passkeys)
Browser support: All modern browsers support WebAuthn.
Multi-Factor Authentication (MFA)
Add additional verification factors:9
| Factor Type | Examples |
|---|---|
| Knowledge | Password, PIN |
| Possession | Phone, security key |
| Inherence | Fingerprint, face |
TOTP (Time-Based One-Time Password)
Standard for authenticator apps (Google Authenticator, Authy):10
- Generate shared secret during setup
- User scans QR code with authenticator app
- App generates 6-digit codes every 30 seconds
- User enters current code to verify
Implementation:
- Use a library (speakeasy, otplib for Node.js)
- Store secret encrypted in database
- Allow backup codes
SMS Codes
Send verification code via SMS.
Concerns:
- SIM swapping attacks
- SS7 vulnerabilities
- NIST discourages SMS for sensitive applications11
Still acceptable for:
- Low-sensitivity applications
- Better than no MFA
- User preference/accessibility
Security Keys (FIDO2)
Hardware tokens using WebAuthn:
- YubiKey
- Google Titan Key
- Built-in platform authenticators
Strongest protection: Phishing-resistant, hardware-backed.
Password Security
If using passwords, follow these guidelines.
Storage Requirements
Never store plaintext passwords. Per OWASP:12
Use adaptive hashing:
- bcrypt (recommended, cost factor 10+)
- Argon2id (modern, memory-hard)
- scrypt (memory-hard alternative)
Do NOT use:
- MD5, SHA1, SHA256 alone
- Encryption (reversible)
- Base64 encoding
Password Requirements
NIST 800-63B guidelines:11
Do:
- Minimum 8 characters (12+ recommended)
- Check against breached password lists
- Allow paste in password fields
- Show password strength meter
Don’t:
- Require special characters
- Force periodic changes
- Use security questions
- Limit password length excessively
Example: Password Hashing (Node.js)
const bcrypt = require('bcrypt');
const SALT_ROUNDS = 12;
// Hash password
async function hashPassword(password) {
return await bcrypt.hash(password, SALT_ROUNDS);
}
// Verify password
async function verifyPassword(password, hash) {
return await bcrypt.compare(password, hash);
}
Security Best Practices
OWASP Authentication Recommendations12
- Use HTTPS everywhere - Encrypt all traffic
- Implement rate limiting - Prevent brute force
- Generic error messages - “Invalid credentials” not “User not found”
- Account lockout - After failed attempts, with unlocking mechanism
- Secure session management - See session section
- Log authentication events - For security monitoring
- Implement MFA - Especially for sensitive operations
Common Vulnerabilities
| Vulnerability | Prevention |
|---|---|
| Credential stuffing | Rate limiting, MFA, breach detection |
| Brute force | Account lockout, rate limiting, CAPTCHA |
| Session hijacking | HTTPS, secure cookies, session binding |
| Session fixation | Regenerate session ID on login |
| XSS token theft | HttpOnly cookies, CSP |
| CSRF | SameSite cookies, CSRF tokens |
Security Headers
Implement these headers:
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Implementation Decision Guide
Choose Session-Based When:
- Building traditional multi-page apps
- Team is less experienced with auth
- Need easy session invalidation
- Single-domain application
Choose JWT When:
- Building APIs for multiple clients
- Microservices architecture
- Mobile app backend
- Stateless scaling required
Choose OAuth/Social Login When:
- Want to reduce friction
- Users already have Google/GitHub/etc.
- Don’t want to manage passwords
- Need access to provider APIs
Choose Passwordless When:
- Prioritizing user experience
- Security is paramount (passkeys)
- Target audience prefers it
- Email/device is trusted
Recommended Auth Libraries
JavaScript/TypeScript
| Library | Use Case |
|---|---|
| Auth.js (NextAuth) | Next.js, SvelteKit, etc. |
| Lucia | Framework-agnostic, TypeScript |
| Passport.js | Express middleware |
| jose | JWT handling |
Python
| Library | Use Case |
|---|---|
| Flask-Login | Flask sessions |
| Django Auth | Django built-in |
| Authlib | OAuth/OIDC |
| PyJWT | JWT handling |
Managed Services
| Service | Strengths |
|---|---|
| Auth0 | Feature-rich, enterprise |
| Clerk | Modern DX, React-focused |
| Supabase Auth | Pairs with Supabase DB |
| Firebase Auth | Google ecosystem |
| AWS Cognito | AWS integration |
Summary
| Method | Security | Complexity | Best For |
|---|---|---|---|
| Sessions | Good | Low | Web apps |
| JWT | Good | Medium | APIs |
| OAuth | Good | Medium | Social login |
| Passwordless | Excellent | Medium | Modern UX |
| MFA | Excellent | Medium | High security |
General recommendations:
- Start with sessions for web apps
- Add OAuth for social login
- Implement MFA for sensitive accounts
- Consider passwordless for modern UX
- Always follow OWASP guidelines
Further Reading
- Related: Modern Tech Stack Guide
- Related: Database Selection Guide
- OWASP Authentication Cheat Sheet
- JWT.io - JWT debugging and libraries
- OAuth 2.0 Specification
- WebAuthn Guide
References
Footnotes
-
OWASP. “Session Management Cheat Sheet.” https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html ↩
-
IETF. “RFC 7519 - JSON Web Token (JWT).” https://datatracker.ietf.org/doc/html/rfc7519 ↩ ↩2
-
OWASP. “JSON Web Token Cheat Sheet.” https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html ↩ ↩2
-
IETF. “RFC 6749 - The OAuth 2.0 Authorization Framework.” https://datatracker.ietf.org/doc/html/rfc6749 ↩
-
OpenID Foundation. “OpenID Connect Core 1.0.” https://openid.net/specs/openid-connect-core-1_0.html ↩
-
IETF. “RFC 7636 - Proof Key for Code Exchange (PKCE).” https://datatracker.ietf.org/doc/html/rfc7636 ↩
-
Auth0. “Documentation.” https://auth0.com/docs ↩
-
W3C. “Web Authentication (WebAuthn).” https://www.w3.org/TR/webauthn-2/ ↩
-
NIST. “Digital Identity Guidelines - Authentication.” https://pages.nist.gov/800-63-3/sp800-63b.html ↩
-
IETF. “RFC 6238 - TOTP: Time-Based One-Time Password Algorithm.” https://datatracker.ietf.org/doc/html/rfc6238 ↩
-
NIST. “SP 800-63B - Digital Identity Guidelines.” https://pages.nist.gov/800-63-3/sp800-63b.html ↩ ↩2
-
OWASP. “Authentication Cheat Sheet.” https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html ↩ ↩2