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 ← SignatureHeader: 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.
Verify the token on every request
Login returns a short-lived JWT; each protected request sends it back for signature and expiry checks.
The client sends an email and password to log in.
Text version
- The client sends an email and password to log in.
- The server checks the credentials before issuing a token.
- The server returns a signed access token that expires in fifteen minutes.
- The client sends the token in the Authorization header to request its profile.
- The server checks the signature and expiry before trusting the claims.
- The verified claims identify user forty-two with the admin role.
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. This works until the refresh token expires or is revoked (up to 7 days in the example above). Implement refresh token rotation: invalidate the old refresh token when a new one is issued.
Stateless checks trade off easy revocation
JWTs let each node verify requests independently, while server sessions make revocation easier through stored state.
JWTs carry signed claims, while server sessions keep state on the server.
Text version
- JWTs carry signed claims, while server sessions keep state on the server.
- Deleting a session revokes it, while a JWT remains valid until expiry unless extra revocation state is added.
- Any node with the key verifies a JWT locally, but sessions need a shared store such as Redis.
- JWTs grow as claims are added, while a session cookie needs only the session ID.
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 ContentJWT verification rejects a tampered payload
Python signs claims with HS256 and a fifteen-minute expiry, then verifies them; changing the payload without signing again causes rejection.
The issue function receives user forty-two and the admin role.
Text version
- The issue function receives user forty-two and the admin role.
- The payload records the subject, role, issue time, and expiry.
- Signing produces an HS256 token with a fifteen-minute lifetime.
- The raw token has three parts: header, payload, and signature.
- Decode checks the signature and expiry with HS256 explicitly allowed, then returns the claims.
- Changing the payload role without signing again makes decode reject the token with a signature error.
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?