Skip to main content

Authentication & Authorization ๐Ÿ”

Authentication verifies who you are. Authorization determines what you can do. Together they form the security backbone of every API.


JWT (JSON Web Token)โ€‹

JWT is a compact, URL-safe token format for representing claims between two parties. It's self-contained โ€” the payload carries all necessary user information, eliminating server-side session storage.

Structureโ€‹

eyJhbGciOi.eyJzdWIiOiI.SflKxwRJS
โ”‚ โ”‚ โ”‚
Header Payload Signature

Header โ€” algorithm & token type:

{ "alg": "HS256", "typ": "JWT" }

Payload โ€” claims about the user:

{
"sub": "1234567890", // subject (user ID)
"name": "Alice",
"role": "admin",
"iat": 1710000000, // issued at
"exp": 1710003600 // expiration
}

Signature โ€” prevents tampering:

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

Best Practicesโ€‹

  • Short-lived access tokens โ€” 15โ€“60 minutes. Use refresh tokens for longer sessions.
  • Store securely: access token in memory (variable/closure), refresh token in HttpOnly; Secure; SameSite=Strict cookie.
  • Never store tokens in localStorage โ€” it's accessible to any JavaScript on the page (XSS vulnerable).
  • Use RS256 (asymmetric) for distributed systems โ€” services can verify with the public key without sharing a secret.
  • Validate all claims: exp (expiry), iss (issuer), aud (audience), nbf (not before).
  • Include only what's needed in the payload โ€” don't stuff the token with data the server already knows.

Access Token + Refresh Token Flowโ€‹

1. POST /login { email, password }
2. Server returns { accessToken, refreshToken }
3. Client sends accessToken in Authorization: Bearer <token>
4. When accessToken expires (401) โ†’ POST /refresh { refreshToken }
5. Server returns new { accessToken, refreshToken }
6. If refreshToken expired/revoked โ†’ redirect to login

Token Validation Middleware (Express)โ€‹

const jwt = require('jsonwebtoken');

function authenticate(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}

try {
const token = header.split(' ')[1];
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}

OAuth 2.0 ๐Ÿ›ก๏ธโ€‹

An open standard for access delegation โ€” allows users to grant third-party applications access to their resources without sharing their credentials.

Grant Typesโ€‹

FlowUse CaseSecurity
Authorization Code + PKCESPAs, mobile apps, native appsโœ… Most secure for public clients
Client CredentialsMachine-to-machine, service accountsโœ… No user involved
Authorization CodeServer-rendered apps (confidential clients)โœ… Requires client secret
Device CodeTVs, IoT devices with limited inputโœ… Secure for input-constrained devices
ImplicitLegacy SPAsโŒ Deprecated โ€” use PKCE instead
PasswordMigrating legacy systemsโŒ Deprecated โ€” anti-pattern

Authorization Code + PKCE Flowโ€‹

1. Client generates code_verifier (random string) and code_challenge (SHA256 hash)
2. GET /authorize?response_type=code&client_id=...&code_challenge=...&code_challenge_method=S256
3. User authenticates at authorization server
4. Browser redirects to callback with ?code=abc123
5. POST /token { code, code_verifier, grant_type: 'authorization_code' }
6. Server returns { access_token, refresh_token, id_token (if OpenID Connect) }

OpenID Connect (OIDC) ๐Ÿ†”โ€‹

An identity layer built on top of OAuth 2.0. It adds authentication (who you are) to OAuth's authorization (what you can access).

Key additions over OAuth 2.0:

  • ID Token โ€” a JWT containing user identity claims (sub, name, email, preferred_username)
  • UserInfo endpoint โ€” GET /userinfo returns current user's claims
  • Standardized scopes: openid (required), profile, email, address, phone

Sessions & Cookies ๐Ÿชโ€‹

Server-side session management stores user state on the server, identified by a session ID sent via a cookie.

How It Worksโ€‹

1. POST /login { email, password }
2. Server creates session โ†’ stores { userId, role, createdAt } in Redis/DB
3. Server responds with Set-Cookie: sessionId=abc123; HttpOnly; Secure; SameSite=Lax
4. Browser automatically sends cookie on every subsequent request
5. Server looks up session โ†’ attaches user object to request
6. POST /logout โ†’ server deletes session โ†’ clears cookie

Security Flagsโ€‹

FlagPurpose
HttpOnlyPrevents JavaScript access (document.cookie) โ€” mitigates XSS
SecureCookie only sent over HTTPS
SameSite=StrictNo cross-site requests โ€” strongest CSRF protection
SameSite=LaxAllows top-level navigation GET requests โ€” good balance for most apps
SameSite=NoneCross-site requests allowed (must also have Secure) โ€” use for iframe/auth flows
DomainRestrict to specific domain (omit for exact-host-only)
PathRestrict to specific path (default /)

Session Storageโ€‹

BackendProsCons
RedisFast, TTL built-in, clusteringData in memory (volatile unless persisted)
Memory (dev only)Zero setupLost on restart, doesn't scale horizontally
Database (Postgres/MySQL)Durable, existing infraSlower, sessions aren't relational data

Session vs JWT Trade-offsโ€‹

CriteriaSessionJWT
StateStateful (server stores)Stateless (token contains all data)
RevocationInstant โ€” delete sessionRequires blocklist or short expiry
Horizontal scalingRequires shared session store (Redis)Any server can verify with public key
Payload sizeCookie is just an IDToken carries claims (larger header)
Mobile/native supportCookie handling variesWorks everywhere (Bearer header)
Logout of all devicesDelete all sessions for userRotate signing key or use blocklist

โ† Back to Backend Engineering ยท ยฉ sparshjaswal