6 min read
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Authentication & Authorization
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening
Web Backend Fundamentals Index
Tier 1 -- Core Web & API Basics
Authentication & Authorization
Tier 2 -- Communication Models
Tier 3 -- Production & API Hardening
Authentication and JWT
1. What It Is
Authentication is the process of verifying that a request comes from who it claims to be. In web backends, this almost always means: a user provides credentials, the server verifies them, and then issues a token or that the client sends on every subsequent request.
(JSON Web Token) — RFC 7519 — is a compact, self-contained token format widely used for authentication. Unlike server-side sessions, a encodes user identity and claims directly in the token, signed by the server so the server can verify it without looking up anything in a database.
Why it matters: every backend that has a login page, an API with access control, or a mobile app needs authentication. JWT is the dominant stateless auth mechanism in modern APIs.
A backend API uses JWTs for authentication. When a protected endpoint receives a request with a JWT, what does the server need to do to confirm the request is legitimate?
2. How It Works in Practice
JWT Structure
A is three Base64url-encoded segments joined by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. ← Header
eyJ1c2VySWQiOjQyLCJyb2xlIjoiYWRtaW4ifQ. ← Payload (claims)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c ← Signature
Header: algorithm (e.g., HS256, RS256) and token type.
Payload (claims):
sub— subject (user ID)iat— issued at (Unix timestamp)exp— expiry- custom claims:
role,email,tenantId, etc.
Signature: computed from header + payload using a secret key (HMAC) or private key (RSA/ECDSA). Tampering with the payload breaks the signature.
Authentication Flow
Client Server
| |
|-- POST /auth/login ---------->|
| { email, password } | 1. Verify credentials
| | 2. Issue JWT (exp: 15 min)
|<-- 200 OK --------------------|
| { access_token: "eyJ..." } |
| |
|-- GET /api/me --------------->|
| Authorization: Bearer eyJ...| 3. Verify signature + expiry
| | 4. Extract claims from payload
|<-- 200 OK --------------------|
| { id: 42, role: "admin" } |
The server never stores the . It just re-verifies the signature on every request.
Refresh Token Pattern
Short-lived access tokens (15 min) paired with long-lived refresh tokens (7 days) stored securely:
- Login → server issues
access_token(short TTL) +refresh_token(long TTL, stored in HttpOnly ). - Client uses
access_tokeninAuthorizationheader. - When
access_tokenexpires → client hits/auth/refreshwith therefresh_token. - Server issues a new
access_token(and optionally rotatesrefresh_token). - If user logs out → server invalidates the
refresh_tokenin its database.
A user logs into your API and receives a JWT access token that expires in 15 minutes. Twenty minutes later, the user makes a request with that token and receives a 401 Unauthorized response. Your system uses the refresh token pattern. What should the client do next to restore the user's session without requiring them to log in again?
3. Common Real-World Usage
SPA + API: the frontend JavaScript app stores the access token in memory (not localStorage), attaches it to every API call in the Authorization: Bearer <token> header.
Mobile apps: tokens stored in secure platform storage (Keychain on iOS, Keystore on Android).
Microservices: each service verifies the independently without calling a central auth server — this is the main scalability benefit.
Libraries:
- Node.js:
jsonwebtoken,jose - Python:
python-jose,PyJWT - Java:
nimbus-jose- - Go:
golang-jwt/jwt
A team is building a microservices architecture where multiple services need to authenticate incoming requests. They are deciding between two approaches: (A) each service calls a central authentication server to validate every token, or (B) each service independently verifies the JWT using a shared public key. What is the primary scalability advantage of approach B?
4. Trade-offs and Common Mistakes
Trade-offs: JWT vs. Server Sessions
| JWT | Server Session | |
|---|---|---|
| State | Stateless (no DB lookup) | Server stores state |
| Logout/revocation | Hard (token valid until expiry) | Easy (delete session) |
| Scale | Easy — any node validates independently | Requires shared session store (Redis) |
| Payload size | Grows with claims | Tiny (just a session ID) |
| Security surface | Token theft = impersonation until expiry | Session theft same risk; easier to revoke |
Common Mistakes
Storing in localStorage: JavaScript-accessible, so any vulnerability can steal it. Prefer in-memory storage for access tokens; use HttpOnly cookies for refresh tokens.
Long-lived access tokens without refresh: a 30-day access token that cannot be revoked is a security nightmare. Keep access tokens short (5–30 min).
Skipping expiry validation: always check exp. Libraries do this for you if you use them correctly; don't write raw verification by hand unless you know what you're doing.
Trusting the alg header: a classic attack is sending alg: none to bypass signature verification. Always specify the algorithm explicitly server-side — never accept what the token says.
Putting sensitive data in the payload: the payload is Base64-encoded, not encrypted. Anyone with the token can decode it. Never put passwords, secrets, or sensitive PII in claims.
Not rotating refresh tokens: if a refresh token is leaked, an attacker can generate new access tokens indefinitely. Implement refresh token rotation: invalidate the old refresh token when a new one is issued.
Your API issues JWTs with a 30-day expiry. A user's token is stolen in a data breach. What is the most significant security problem with this setup, and what is the recommended mitigation?
5. Interview Angle
How to explain it
" is a signed token. The server issues it after login, and the client sends it on every request. The server verifies the signature cryptographically — no database lookup needed. The key trade-off vs. sessions is that JWTs are hard to revoke: you can't invalidate them before they expire unless you maintain a token blacklist, which kills the statelessness benefit."
Common interview questions
Q: How does work at a high level? A: Three parts — header, payload, signature — joined by dots. The server signs the header + payload with a secret or private key. On each request, the server re-verifies the signature and checks expiry. No server-side storage needed.
Q: How do you handle logout with JWTs? A: Short access token TTL (15 min) so they expire quickly. For immediate revocation: maintain a token blacklist (Redis set of revoked JTIs) or use refresh token invalidation — the access token expires soon anyway.
Q: HS256 vs. RS256 — when would you choose each? A: HS256 uses a shared secret — simpler, but every service that verifies tokens needs the secret. RS256 uses a private key to sign and a public key to verify — services only need the public key, making it better for microservices or when third parties verify tokens.
Q: Where should you store JWTs on the client? A: Access tokens in memory (not localStorage — risk). Refresh tokens in HttpOnly, Secure, SameSite=Strict cookies — inaccessible to JavaScript.
Q: What are JWT claims?
A: Key-value pairs in the payload. Standard claims: sub (subject/user ID), iat (issued at), exp (expiry). Custom claims: role, tenantId, etc. Claims are signed but not encrypted.
Your team is building a microservices architecture where multiple independent services need to verify authentication tokens, and some of those services are maintained by a third-party vendor. Which JWT signing algorithm is the better fit, and why?
6. Practical Examples
Node.js: issue and verify JWTs
const jwt = require('jsonwebtoken'); const SECRET = process.env.JWT_SECRET; // Issue on login function issueToken(userId, role) { return jwt.sign( { sub: userId, role }, SECRET, { algorithm: 'HS256', expiresIn: '15m' } ); } // Middleware to verify function authMiddleware(req, res, next) { const header = req.headers.authorization; if (!header?.startsWith('Bearer ')) return res.status(401).json({ error: 'Unauthorized' }); const token = header.slice(7); try { const payload = jwt.verify(token, SECRET, { algorithms: ['HS256'] }); req.user = payload; next(); } catch (err) { res.status(401).json({ error: 'Invalid or expired token' }); } }
Python: issue and verify
import jwt from datetime import datetime, timezone, timedelta SECRET = "your-secret-key" def issue_token(user_id: int, role: str) -> str: payload = { "sub": str(user_id), "role": role, "iat": datetime.now(timezone.utc), "exp": datetime.now(timezone.utc) + timedelta(minutes=15), } return jwt.encode(payload, SECRET, algorithm="HS256") def verify_token(token: str) -> dict: # Raises jwt.ExpiredSignatureError, jwt.InvalidTokenError on failure return jwt.decode(token, SECRET, algorithms=["HS256"])
Refresh token flow (pseudocode)
POST /auth/login
→ { access_token: "eyJ...15min", refresh_token: "opaque-uuid-stored-in-db" }
└─ refresh_token goes into HttpOnly cookie
POST /auth/refresh
Cookie: refresh_token=opaque-uuid
→ Server looks up uuid in refresh_tokens table (checks valid, not revoked, not expired)
→ Rotate: delete old uuid, create new uuid
→ { access_token: "eyJ...new-15min" }
POST /auth/logout
Cookie: refresh_token=opaque-uuid
→ Server deletes uuid from refresh_tokens table
→ 204 No Content
In a refresh token flow, a user's refresh token is stored as an opaque UUID in a database rather than as a self-contained signed JWT. When the user calls POST /auth/refresh, the server rotates the token — deleting the old UUID and issuing a new one. What is the primary security advantage of this rotation strategy?
Glossary History
Click dotted jargon to save explanations here.
Glossary History
Click dotted jargon to save explanations here.