Understanding JSON Web Tokens (JWT) and token security
JSON Web Tokens (JWTs) have become the default standard for stateless authentication in modern web applications, microservices, and single-page apps. Yet a pervasive misunderstanding remains: many developers treat a JWT as an encrypted, secret container.
A standard JWT is merely signed and Base64URL-encoded — anyone who intercepts it can read its full payload instantly. This guide breaks down how the three parts of a token function, how cryptographic verification works, and the most common architectural mistakes that compromise token-based auth.
The three parts of a JWT
A JWT consists of three strings separated by dots: header.payload.signature.
Header: A JSON object specifying the token type ('JWT') and the signing algorithm (such as HS256 or RS256), Base64URL-encoded.
Payload: The claims — user ID, role, issued-at timestamp ('iat'), and expiration ('exp'), Base64URL-encoded. Because this is simply encoding and not encryption, you must never store sensitive secrets like passwords, credit card details, or private API keys inside a JWT payload.
Signature: Created by taking the encoded header, a dot, the encoded payload, and signing them with a cryptographic secret or private key. The signature guarantees data integrity: if an attacker modifies the payload to grant themselves admin privileges, the signature calculation will fail.
Symmetric (HS256) versus Asymmetric (RS256) signing
Choosing between symmetric and asymmetric algorithms depends on your system architecture:
| Algorithm | Key Type | Best Suited For | Trade-off |
|---|---|---|---|
| HS256 (HMAC-SHA256) | Single shared secret | Monolithic apps where same server issues and verifies | Every service that verifies tokens must know the signing secret |
| RS256 (RSA Signature) | Private key / Public key pair | Microservices, third-party APIs, OAuth2 / OIDC | Slightly higher compute overhead; public key can be shared openly |
| ES256 (ECDSA) | Elliptic curve key pair | High-throughput mobile and edge APIs | Shorter signatures with equivalent cryptographic strength |
In distributed microservice architectures, RS256 or ES256 is preferred because consumer services only need the public key (often published via JWKS) to verify authenticity, without the risk of leaking the signing key.
Critical vulnerabilities to defend against
- The 'none' algorithm bypass: Early JWT libraries allowed the header to declare 'alg': 'none', causing buggy verification functions to treat un-signed tokens as valid. Always enforce acceptable algorithms explicitly in backend verification code.
- Weak HS256 secrets: Using short or predictable signing secrets (like 'secret123') allows attackers to brute-force the secret offline using tools like Hashcat in seconds. Signing secrets should be at least 256 bits of high-entropy randomness.
- Storage vulnerabilities (LocalStorage vs HttpOnly cookies): Storing JWTs in browser localStorage makes them accessible to any JavaScript running on the page, exposing them to Cross-Site Scripting (XSS) attacks. Storing tokens in HttpOnly, Secure, SameSite cookies protects them from script access.
The refresh token rotation pattern
Because JWTs are stateless, you cannot easily revoke a token before it expires without maintaining a database blacklist (which defeats the stateless advantage). The standard solution is a dual-token pattern:
- Access token: Short lifespan (5 to 15 minutes). Used for authorization on API requests.
- Refresh token: Longer lifespan (7 to 30 days). Stored in an HttpOnly secure cookie and kept in the database.
- When the access token expires, the client sends the refresh token to issue a new access token.
- Implement refresh token rotation: Each time a refresh token is used, invalidate it and issue a new pair. If an invalidated refresh token is ever presented again, revoke the entire session family immediately, as it indicates a stolen token.
Frequently asked questions
- Is a JWT encrypted?
- No. Standard JWS (JSON Web Signature) tokens are signed, not encrypted. Their content is publicly readable by anyone who decodes the Base64 string. If you need confidential payloads, use JWE (JSON Web Encryption).
- How can I inspect what's inside a JWT without sending it to a server?
- You can decode it directly in your browser using our client-side JWT Decoder. Because decoding only requires splitting the dots and Base64-decoding the JSON strings, your token never leaves your machine.
- What does 'sub', 'iss', and 'exp' mean?
- These are standard registered claim names defined in RFC 7519: 'sub' (subject, typically the user ID), 'iss' (issuer, the auth server URL), and 'exp' (expiration Unix timestamp).
Tools for this
More guides
- Practical password security, without the folkloreWhy length beats symbols, why scheduled password changes were abandoned, and the three habits that actually prevent account compromise.
- How loan interest actually worksWhy your early repayments barely touch the principal, what a 'flat rate' quote is really costing you, and how to compare two loan offers on the number that matters.