In the early days of web application development, state management was straightforward. A user logged in, the server authenticated their credentials, allocated a chunk of memory (a session) on the backend database or in-memory cache, and handed the client a small session identifier cookie. Every subsequent HTTP request sent this session ID back to the server, which looked up the user state in real time.
As software architecture evolved from monolithic, single-server setups to distributed microservices and serverless infrastructure, this centralized state lookup became a severe bottleneck. Querying a centralized Redis cluster or database on every single API request introduces latency, increases server costs, and introduces a single point of failure.
In 2015, the Internet Engineering Task Force (IETF) formalized RFC 7519, standardizing JSON Web Tokens (JWTs). JWTs introduced a stateless paradigm shift: instead of storing session state on the server, the server cryptographically signs the state, hands it to the client, and forgets about it.
Understanding how JWTs achieve identity verification without backend storage requires examining their structural composition, encoding schemes, cryptographic signing mechanics, and structural vulnerabilities.
1. The Paradigm Shift: From Stateful Sessions to Stateless Tokens
To understand why JWTs are ubiquitous in modern single-page applications (SPAs), mobile apps, and microservice architectures, we must contrast Stateful Authentication with Stateless Authorization.
Stateful Session Architecture:
[ Client ] ---> Request + SessionID ---> [ Monolithic Server ] ---> Query Lookup DB/Redis
<--- User Data Returned
Stateless JWT Architecture:
[ Client ] ---> Request + JWT Token ---> [ API Gateway / Microservice ] (Verifies Signature Locally)
---> Process RequestStateful Sessions
- State Storage: Stored on server memory, key-value stores (e.g., Redis), or databases.
- Scalability: Requires sticky sessions or synchronized central session caches across microservice instances.
- Revocation: Instantaneous. Deleting the session key from Redis instantly logs out the user.
- Payload Size: Minimal (typically a 32-character random UUID string).
Stateless JWTs
- State Storage: Stored on the client (e.g., in memory or httpOnly cookies).
- Scalability: High horizontal scalability. Any microservice with access to the public key or shared secret can verify the token without querying a database.
- Revocation: Complex. Because tokens are self-contained, they remain valid until their expiration timestamp (
exp) unless explicit revocation lists (blacklists) or refresh token rotations are used. - Payload Size: Moderate to Large (contains claims, scopes, timestamps, and signature overhead).
2. Anatomy of a JWT: Header, Payload, and Signature
A JSON Web Token consists of three distinct segments separated by periods (.):
$$\text{JWT} = \text{Base64Url}(\text{Header}) + "." + \text{Base64Url}(\text{Payload}) + "." + \text{Signature}$$
When presented in an HTTP Authorization header, a typical Bearer token looks like this:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cLet's dissect each of the three components.
Segment 1: The Header
The header informs the recipient how the token was constructed and how to verify its signature. It is a JSON object containing at least two claims:
alg: The cryptographic algorithm used to sign the token (e.g.,HS256,RS256,ES256).typ: The token type, which is standardly set to"JWT".
{
"alg": "HS256",
"typ": "JWT"
}Segment 2: The Payload (Claims)
The payload contains the claims—statements about an entity (typically the user) along with contextual metadata. RFC 7519 defines three categories of claims:
Registered Claims: Pre-defined, standardized claims that provide high-interoperability metadata.
iss(Issuer): The principal that issued the token (e.g.,https://auth.daylogic.org).sub(Subject): The subject of the token (e.g., user IDusr_8f92a10b).aud(Audience): The intended recipient of the token (e.g.,https://api.daylogic.org).exp(Expiration Time): Unix timestamp after which the token MUST NOT be accepted.nbf(Not Before): Unix timestamp before which the token MUST NOT be accepted.iat(Issued At): Unix timestamp indicating when the token was created.jti(JWT ID): Unique identifier for the token, used to prevent replay attacks.
Public Claims: Custom attributes defined by application developers, designed to avoid collisions by using collision-resistant namespaces (e.g., URIs).
Private Claims: Custom attributes shared between parties that agree on using them (e.g.,
role,org_id).
{
"sub": "usr_98410294812",
"name": "Jane Doe",
"admin": true,
"iat": 1771891200,
"exp": 1771894800
}**Critical Note:** The payload of a standard JWT is **encoded, not encrypted**. Anyone who intercepts a JWT can decode the Base64Url string and read its payload contents. Sensitive data like passwords, payment info, or PII must **never** be stored in an unencrypted JWT payload.
Segment 3: The Signature
The signature ensures that the token has not been altered in transit. It is calculated by taking the encoded header, the encoded payload, a secret key (or private key), and hashing them using the algorithm specified in the header.
For an HS256 (HMAC-SHA256) signature, the mathematical operation is:
$$\text{Signature} = \text{HMAC-SHA256}\Big(\text{Base64Url}(\text{Header}) + "." + \text{Base64Url}(\text{Payload}), \text{SecretKey}\Big)$$
If an attacker changes the admin: false claim in the payload to admin: true, the resulting Base64Url payload string changes. When the backend application recomputes the signature using its secret key, the computed signature will not match the token's signature segment, causing the application to reject the request.
3. Base64 vs. Base64Url Encoding Mechanics
A common point of confusion for developers is why standard Base64 encoding is not used directly in JWTs.
Standard Base64 encoding (RFC 4648) maps 6-bit binary sequences to a set of 64 ASCII characters: A-Z, a-z, 0-9, +, and /. It uses the = character for padding when the input byte length is not divisible by 3.
Standard Base64 Character Set: [ A-Z ] [ a-z ] [ 0-9 ] + / =
Base64Url Character Set: [ A-Z ] [ a-z ] [ 0-9 ] - _ (No Padding)In HTTP communications, standard Base64 characters pose transmission risks:
- URL Parameter Interference: The
+character is interpreted as a URL space (%20). - Routing Conflicts: The
/character acts as a path delimiter in URL endpoints. - HTTP Header Overhead: Padding characters (
=) require URL-encoding (%3D) when appended to GET parameter strings.
To solve this, Base64Url encoding replaces problematic characters:
- Replace
+with hyphen-(ASCII 45) - Replace
/with underscore_(ASCII 95) - Omit trailing padding
=characters entirely
When a system decodes a Base64Url string, it calculates the missing modulo-4 padding characters (=) based on string length before converting the binary payload back into JSON data.
4. Cryptographic Algorithms: Symmetric vs. Asymmetric Signing
JWT signatures rely on two main cryptographic paradigms: Symmetric Key Encryption and Asymmetric Public-Key Encryption.
Symmetric (HS256):
[ Auth Server ] --- (Signs with Secret Key "K") ---> [ JWT ] ---> [ API Service ] (Verifies with Secret Key "K")
Asymmetric (RS256):
[ Auth Server ] --- (Signs with Private Key) ---> [ JWT ] ---> [ API Service ] (Verifies with Public Key)Symmetric Signing (HMAC Algorithms: HS256, HS384, HS512)
Symmetric signing uses a single shared secret key for both generating the signature and verifying it.
- Pros: Extremely fast processing speed, minimal computational overhead, simple key management.
- Cons: Every microservice or API instance that needs to verify the token must possess the secret key. If a downstream microservice is compromised, an attacker gains the capability to issue valid tokens for the entire system.
Asymmetric Signing (RSA & ECDSA Algorithms: RS256, ES256)
Asymmetric algorithms utilize a asymmetric key pair: a Private Key (kept strictly on the authorization server) and a Public Key (distributed freely to downstream API services).
- Pros: Secure trust boundaries. Receiving microservices only hold the public key; they can verify tokens with absolute certainty, but can never forge or issue new tokens.
- Cons: Slower signature computation and verification compared to symmetric HMAC operations.
Algorithmic Comparison Table
| Algorithm | Type | Key Size / Curve | Cryptographic Primitive | Best Used For |
|---|---|---|---|---|
| HS256 | Symmetric | Minimum 256 bits | HMAC + SHA-256 | Monolithic architecture, internal microservices with shared trust. |
| RS256 | Asymmetric | Minimum 2048 bits | RSA Signature + SHA-256 | Enterprise OAuth 2.0 / OIDC identity providers (Auth0, Okta). |
| ES256 | Asymmetric | P-256 Curve | ECDSA + SHA-256 | High-performance API gateways needing compact signatures and lower CPU usage than RSA. |
| EdDSA | Asymmetric | Curve25519 | Edwards-curve Digital Signature | Modern high-security distributed systems requiring fast signing & small keys. |
5. Security Pitfalls & Critical Vulnerabilities
Because JWT implementations are decentralized, small misconfigurations can lead to severe system compromises. Below are four common security vulnerabilities and how to mitigate them.
1. The alg: "none" Vulnerability
Early JWT implementations supported an algorithm setting of "none". This was intended for un-signed tokens in controlled environments. However, attackers exploited vulnerable verification libraries by stripping the signature from a valid JWT and updating the header to:
{
"alg": "none",
"typ": "JWT"
}If the server library accepted "none" without explicit checks, it bypassed signature verification altogether, trusting whatever payload the attacker supplied.
Mitigation: Ensure your verification code explicitly whitelists allowed algorithms (e.g., jwt.verify(token, key, { algorithms: ['RS256'] })).
2. The Algorithm Confusion Attack (RS256 to HS256)
If a server expects an asymmetric RS256 token, it uses a public key to verify the token. In an algorithm confusion attack, an attacker alters the header to specify symmetric HS256.
If the backend validation library dynamically reads the alg header without enforcement, it will attempt to verify an HS256 signature using the server's public key as the HMAC secret key. Because the public key is publicly accessible, the attacker can sign a forged payload locally using that public key as their secret, producing a valid HS256 signature that the backend accepts.
// VULNERABLE IMPLEMENTATION
// Accepts whatever algorithm the token header requests
const payload = jwt.verify(token, publicKey);
// SECURE IMPLEMENTATION
// Explicitly enforces expected signing algorithm
const payload = jwt.verify(token, publicKey, { algorithms: ['RS256'] });3. Token Storage Risks: LocalStorage vs. HttpOnly Cookies
Where a client application stores a JWT dictates its resistance to web-based attacks.
Storage Option: localStorage / sessionStorage
- Vulnerable to: Cross-Site Scripting (XSS)
- Mechanism: Any malicious 3rd-party script running in the DOM can read `localStorage.getItem('token')` and exfiltrate it.
Storage Option: SameSite httpOnly Cookie
- Resistant to: XSS (JavaScript cannot access document.cookie)
- Vulnerable to: Cross-Site Request Forgery (CSRF) if SameSite flags are misconfigured.
- Ideal Mitigation: Store tokens in httpOnly, Secure, SameSite=Strict cookies.4. Replay Attacks and Stale Tokens
Because valid JWTs are stateless, revoking access prior to the exp timestamp requires extra infrastructure. If a user logs out, their token remains mathematically valid until it expires.
Mitigation Strategies:
- Keep access token lifespans short (e.g., 5 to 15 minutes).
- Pair short-lived access tokens with long-lived Refresh Tokens stored safely in
httpOnlycookies. - Maintain an in-memory revoking table (e.g., Redis bloom filters) tracking revoked
jti(JWT IDs) for high-security scenarios.
6. Implementation Checklist for Developers
When deploying JWT authorization in web applications, apply these security standards:
[ Security Verification Matrix ]
├── 1. Cryptography
│ ├── Use RS256 or ES256 for public identity providers.
│ └── Minimum secret length of 256 bits for HS256 algorithms.
├── 2. Validation Enforcement
│ ├── Whitelist allowed algorithms explicitly in verify calls.
│ ├── Validate `exp`, `iat`, `nbf`, `iss`, and `aud` claims.
│ └── Reject unencrypted sensitive personal information (PII).
└── 3. Transport & Storage
├── Force HTTPS to prevent network interception.
└── Store access tokens in httpOnly, Secure cookies with SameSite=Strict.By understanding how Base64Url encoding, JSON structures, and cryptographic primitives work together, developers can build fast, stateless, and scalable authorization systems without compromising data integrity.
