Authentication and Authorization
Authentication and Authorization
Authentication (AuthN) answers: "Who are you?"
Authorization (AuthZ) answers: "What are you allowed to do?"
Identity mistakes cause serious security AND usability issues, so adopt simple, well-understood patterns over homegrown crypto.
1. Core Terms
| Term | Meaning |
|---|---|
| Identity Provider (IdP) | System that proves identity (Google, Auth0, Okta) |
| Session | Server-side record binding a user to state (expires, revocable) |
| Token | Signed blob the server trusts (JWT) or opaque ID referencing state |
| Claims | Attributes about the user (email, roles, exp) inside a token |
| RBAC | Role-Based Access Control (roles → permissions) |
| ABAC | Attribute-Based (conditions on user/resource) |
| MFA | Multi-Factor Auth (password + OTP, etc.) |
2. Choosing an Approach
| Scenario | Recommended Pattern | Why |
|---|---|---|
| Classic web app (same domain) | Server session + secure httpOnly cookie | Simple, mitigates XSS token theft |
| SPA + API (same top-level domain) | httpOnly cookie (SameSite=Lax/Strict) | Avoid localStorage token exposure |
| Mobile app / public API | Short-lived JWT + refresh token rotation | Scales across native clients |
| Third-party integrations | OAuth2 (authorization code + PKCE) | Delegated permission & consent |
Avoid rolling custom password hashing; use battle-tested libs (bcrypt/argon2) and a framework or managed IdP when possible.
3. Session vs JWT (Trade-offs)
| Aspect | Server Session | Stateless JWT |
|---|---|---|
| Revocation | Easy (delete server record) | Hard (need blacklist or short TTL) |
| Storage | Server memory/DB | Client stores token (header/cookie) |
| Size | Tiny cookie ID | JWT can grow (claims) |
| Rotation | Not required | Refresh flow required |
| Simplicity | Higher (framework support) | More moving parts |
Rule of thumb: Start with server sessions unless you truly require stateless scale across many services without shared session storage.
4. Secure Cookie Attributes
| Attribute | Purpose |
|---|---|
httpOnly |
JS cannot read (mitigate XSS theft) |
Secure |
Only sent over HTTPS |
SameSite=Lax/Strict |
CSRF mitigation |
Path=/api |
Scope cookie to API route if desired |
| Short TTL + rotation | Limits window of stolen cookie usefulness |
Still add CSRF protection (synchronizer token or double-submit cookie) if performing state-changing requests with automatic cookies.
5. OAuth2 + OIDC Simplified
OpenID Connect = OAuth2 + identity layer (ID token with user claims).
sequenceDiagram
participant U as User Browser
participant SPA as Frontend (SPA)
participant IDP as Identity Provider
participant API as Backend API
U->>SPA: Navigate /login
SPA->>IDP: Authorization Code + PKCE (redirect)
IDP-->>U: Login form
U->>IDP: Credentials
IDP-->>SPA: Redirect with code
SPA->>IDP: Exchange code + verifier
IDP-->>SPA: ID Token + Access Token (JSON)
SPA->>API: Request with Access Token (Bearer)
API-->>SPA: Data (after token validation)
Token Types
| Token | Purpose | Audience |
|---|---|---|
| ID Token (JWT) | Authenticates user to client | Client (not API) |
| Access Token | Authorization for APIs | API (resource server) |
| Refresh Token | Obtain new access token | Authorization server |
6. Authorization Models
- RBAC: Users have roles (admin, editor); roles map to permissions. Simple matrix.
- ABAC: Policies evaluate attributes (user.department == resource.department).
- ReBAC: Relationship-based (e.g., user is MEMBER of project). Systems like Google Zanzibar model this.
Start with RBAC (YAML / table mapping). Introduce attribute/policy checks only when necessary.
Example (pseudo policy):
roles:
admin:
- user.read
- user.write
- project.delete
member:
- project.read
- project.update_own
7. API Protection Flow (Cookie Session Example)
- User submits credentials to
/login. - Server validates and creates session row
{session_id, user_id, expires_at}. - Server sets cookie:
Set-Cookie: sid=<opaque>; HttpOnly; Secure; SameSite=Lax; Path=/. - Browser automatically sends cookie on subsequent API calls.
- Middleware loads session → attaches
req.user. - Authorization layer checks role/permissions before handler.
- Logout: server deletes session row + set cookie expired.
8. JWT Validation Checklist
| Step | Description |
|---|---|
| Verify signature | Use correct algorithm (e.g., RS256) & public key |
Check exp & nbf |
Reject expired / not-yet-valid tokens |
Enforce audience (aud) |
Ensure token meant for this API |
Enforce issuer (iss) |
Matches your IdP domain |
| Minimal claims | Include only required data (avoid PII) |
Never trust client-provided roles without verifying signature.
9. Refresh Token Rotation (JWT)
Store refresh tokens securely (httpOnly cookie or secure storage on mobile). On usage:
1. Validate existing refresh token.
2. Issue new access + refresh tokens.
3. Invalidate old refresh token (prevent replay).
4. Detect reuse: if a previously invalidated refresh token appears, revoke entire session.
10. MFA Quick Add
- User enrolls: generate TOTP secret (QR code).
- Store hashed secret server-side.
- On login success (password valid) require 6-digit TOTP.
- Lock after N failures; backup recovery codes for loss scenario.
11. Common Pitfalls & Fixes
| Pitfall | Risk | Fix |
|---|---|---|
| Storing JWT in localStorage | XSS theft | Use httpOnly cookie + CSRF defenses |
| Long-lived access tokens | Hard revocation | Use short TTL + refresh rotation |
| Leaking detailed error messages | Account enumeration | Generic auth error msg |
| Password hashing with SHA256 | Easy cracking | Use bcrypt/argon2 with salt |
| Orphaned sessions | Privilege persistence | Expire & cleanup cron |
12. Example Express Middleware (Conceptual)
// Pseudo-code only
function authSession(req, res, next) {
const sid = req.cookies.sid;
if (!sid) return res.status(401).json({ error: 'UNAUTHENTICATED' });
const session = db.sessions.find(sid);
if (!session || session.expires_at < Date.now()) {
return res.status(401).json({ error: 'SESSION_EXPIRED' });
}
req.user = db.users.find(session.user_id);
next();
}
function requireRole(role) {
return (req, res, next) => {
if (!req.user?.roles.includes(role)) {
return res.status(403).json({ error: 'FORBIDDEN' });
}
next();
}
}
13. Performance & Caching Considerations
- Avoid storing excessive claims in JWT (size impacts header bloat).
- Use a short in-memory cache for user roles/permissions if DB lookups are frequent.
- Invalidate cache on role changes to prevent stale rights.
14. Checklist
- Auth method selected (session vs JWT) & documented
- Secure cookie attributes set (if cookies used)
- Password hashing library (bcrypt/argon2) configured
- CSRF protection for state-changing routes
- Role/permission matrix documented in repo
- Token/Session expiration + rotation policy
- MFA (optional) plan or deferred rationale documented
- Central logout / revocation path works
- Minimal personally identifiable info in tokens
- Logging of auth events (login success/fail, role changes)
15. Further Resources
- OAuth2: https://oauth.net/2
- OpenID Connect: https://openid.net/connect
- Auth0 Docs: https://auth0.com/docs
- OWASP Cheat Sheets (Authentication, Session Management)
- "OAuth 2 in Action" (Book)