As web applications move from monoliths to microservices, traditional session-based cookie verification becomes an infrastructure bottleneck. I wrote this guide to detail the cryptographic mechanics of JSON Web Tokens, demonstrating how stateless authentication maintains security at scale without constant database session lookups.
In session-based authentication, the server generates a session ID, stores it in database memory (or Redis), and sends it to the client. On every HTTP request, the database must be queried to validate that session. When scaling to multiple server instances:
A JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact, self-contained way for securely transmitting information between parties as a JSON object. This information is cryptographically signed, meaning the server can trust the claims without querying a database. It consists of three parts:
The token flow consists of:
1. Credentials Validation: User posts login details.
2. Token Signing: Server verifies, signs a payload, and sets short-lived access tokens.
3. Transmission: Client receives token and attaches it to subsequent API headers (Authorization: Bearer <token>) or HttpOnly cookies.
4. Stateless Verification: Server decodes the token and verifies the signature using its private key or secret. If the signature matches and expiration is valid, the request is authorized.
The architectural flow of stateless token validation compared to stateful session stores is represented below:
[ Client Device ] --( 1. Send Login Credentials )--> [ API Gateway / Load Balancer ]
|
( 2. Routes Authenticator )
|
[ Auth Microservice Node ]
|
( 3. Compare Password )
|
[ MongoDB ]
|
( 4. Sign RS256 Access Token )
|
[ Client Device ] <--( 5. Set Cookie Token payload )-----------'
Subsequent Authorized API Calls:
[ Client Device ] --( 6. Request with JWT httpOnly cookie )--> [ API Server Node ]
|
( 7. Decode & Verify Local Signature )
( No DB Query Required! )
|
( 8. Process & Return Data )
[ Client Device ] <--( 9. JSON Response payload )----------------------'Below is a Node.js / Express implementation demonstrating RS256 token verification:
const express = require('express');
const jwt = require('jsonwebtoken');
const fs = require('fs');
const app = express();
// Load public key for token verification (asymmetric signature)
const publicKey = fs.readFileSync('./keys/public.key', 'utf8');
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer <JWT>
if (!token) {
return res.status(401).json({ error: 'Access token missing' });
}
// Verify the signature asymmetrically using RS256 public key
jwt.verify(token, publicKey, { algorithms: ['RS256'] }, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = user;
next();
});
}
app.get('/api/protected-resource', authenticateToken, (req, res) => {
res.json({ data: 'Sensitive telemetry data', user: req.user });
});localStorage leaves them open to Cross-Site Scripting (XSS) attacks. If an attacker injects a script, they can copy the token.
Mitigation: Store access tokens inside httpOnly, Secure, and SameSite=Strict cookies. This ensures JavaScript cannot read the token, blocking token theft scripts.{"alg":"none"} and skipped signature verification entirely. Always explicitly specify allowed algorithms (e.g. { algorithms: ['RS256'] }) inside verify hooks.I implemented this exact architecture in authservice. The microservice utilizes RS256 private keys to sign authentication payload claims, while downstream servers like civicpulse read the tokens using public keys, maintaining absolute system isolation.
httpOnly and SameSite flags on cookies to lock out scripts from reading auth data.