JWT SECURITY CHECKER
Paste a URL. The scanner collects the tokens your app issues, decodes the claims, and replays tampered variants to see whether your server actually verifies the signature — or just reads it.
TEST YOUR JWT HANDLING NOW
Enter your URL — we capture the tokens your app issues, decode the claims, and replay alg=none and re-signed variants to check whether verification is real.
What is JWT security testing?
A JSON Web Token is three base64 segments: a header naming the signing algorithm, a payload of claims, and a signature. Your server issues one after login and trusts its claims on every subsequent request. The signature is the entire security model — if it can be forged, skipped, or ignored, then “I am user 1 with role admin” is just a string an attacker types.
Testing JWT handling means checking what the server actually does with a token, not what the code says it does. Does it verify at all? Does it pin the algorithm, or read it from attacker-controlled input? Is the secret guessable? Does the token ever stop being valid? And is it stored somewhere a stray script can read?
This scanner collects the tokens your deployed app issues and answers those questions from the outside.
Why this happens in AI-generated apps
AI coding tools — Lovable, Bolt, v0, Cursor, Replit — produce auth code that works on the first try, which is exactly the problem. A login flow that returns a token and a frontend that stores it looks complete in a preview, and nothing in the preview distinguishes verified from decoded.
The recurring shapes are consistent. Verification helpers that read the algorithm from the token header, because the library’s permissive default was never overridden. Signing secrets that are placeholder strings from an example, kept because the app worked and nobody flagged them. Tokens issued with no exp field, because adding expiry means building a refresh flow and the model took the shorter path. Claims stuffed with the user’s email, name, and role, because it saved a database lookup in the component that needed them. And localStorage.setItem('token', ...), because that is the pattern in the most tutorials.
Each is a reasonable-looking line of code. Together they mean a token that never expires, holds personal data in the open, can be read by any script on the page, and may not be verified at all. See JWT alg=none and kid traversal for the exploitation detail, and OWASP Top 10 for AI-generated code for the surrounding failure modes.
What the scanner checks
ALG=NONE ACCEPTED
A token with the signature stripped and alg set to none is replayed. If it is honored, every claim is attacker-controlled.
ALGORITHM CONFUSION
An RS256 token re-signed as HS256 using the public key as the HMAC secret — the classic verification bypass.
WEAK HMAC SECRET
The captured token is tested offline against common, default, and short secrets. A hit means anyone can mint tokens.
MISSING EXPIRY
No exp claim, or an expiry so distant the token is effectively permanent and unrevokable.
EXPIRY NOT ENFORCED
exp is present but an expired token is still accepted — verification skipped the time check.
SENSITIVE CLAIMS
Email, phone, address, internal IDs, plan tier, or permission maps sitting in a payload anyone can decode.
LOCALSTORAGE STORAGE
Token written to localStorage or sessionStorage, reachable by any script including a compromised dependency.
WEAK COOKIE FLAGS
Token in a cookie without httpOnly, Secure, or SameSite — readable or sendable cross-site.
MISSING CLAIM CHECKS
No iss or aud validation, so a token minted for a different service or environment is accepted.
How it works
- Collect — we load your URL in a real headless browser and capture the tokens the app issues and sends.
- Decode — header and payload are parsed to read the algorithm, expiry, and every claim in the clear.
- Test the secret — for HMAC tokens, candidate secrets are checked offline against the captured signature.
- Replay tampered variants —
alg=none, algorithm-confusion, and expired tokens are sent to see what the server accepts. - Inspect storage — we check where the token lives: cookie flags,
localStorage, orsessionStorage. - Report — each finding shows the decoded token, what an attacker can forge, and the verification fix.
JWT configuration reference
| Setting | Safe configuration | If wrong |
|---|---|---|
| Algorithm | Pinned server-side in the verify call | Attacker sets alg: none or swaps RS256 for HS256 and forges any claim |
| HMAC secret | Long, random, from a secret store | Cracked offline; attacker mints valid tokens for any user |
| Signing scheme | Asymmetric (RS256 / ES256) for multi-service setups | Every service that verifies also holds a key that can mint |
exp |
Short-lived — minutes to hours, with refresh | Token valid forever; password changes do not revoke it |
| Expiry enforcement | Verified on every request | Expired token still accepted; expiry is decoration |
iss / aud |
Validated against expected values | A token from staging, or another service, is accepted in production |
| Claim contents | Opaque user id only | Email, role, and internal IDs readable by anyone holding the token |
| Client storage | httpOnly; Secure; SameSite cookie |
localStorage token exfiltrated by any XSS or compromised dependency |
kid header |
Fixed key map, no path lookup | Attacker points key resolution at a file or value they control |
| Key rotation | Supported, with overlapping validity | A leaked secret cannot be retired without logging everyone out at once |
Common fixes
- Pass the expected algorithm explicitly to your verification call and reject every other value. Never let the token’s own header decide how it is checked — that is the root of both
alg=noneand algorithm confusion. - Replace HMAC secrets with a long random value from a secret store, or move to asymmetric signing so services that only verify never hold a key that can mint.
- Set a short
expon every token and build a refresh flow. Without expiry there is no revocation, because a signed token stays valid regardless of what happens to the account. - Validate
issandaudso a token minted for staging, or for a different service sharing the key, is not accepted in production. - Keep claims opaque. Carry a user id and look up everything else server-side; the payload is signed, not encrypted, and is readable by anyone who holds the token.
- Move the token into an
httpOnly; Secure; SameSite=Laxcookie so JavaScript cannot read it. If it must live inlocalStorage, keep expiry short and enforce a strict Content-Security-Policy. - Never trust claims for authorization without a server-side check. A
role: adminclaim is a hint about who the user says they are, not permission to act. - Rotate the signing key after fixing anything. Tokens issued under the broken configuration remain valid until the key that signed them is retired.
Related tools and guides
- CORS Misconfiguration Checker — with bearer tokens, a permissive CORS policy is what lets another site use them.
- .env Exposure Checker — a leaked signing secret makes every token in your app forgeable.
- Source Map Exposure Checker — recovered source reveals exactly how tokens are signed, stored, and checked.
- Token Leak Checker — finds JWTs and provider keys shipped in the frontend bundle.
- Supabase RLS Checker — a correctly verified token still needs row-level authorization behind it.
- Security Headers Checker — CSP and cookie flags are what limit the damage of a stolen token.
- Vibe Code Scanner — full security scan of a deployed AI-generated app.
- JWT alg=none and kid traversal pattern — how the bypass is found and exploited in practice.
- Free Security Self-Audit — manual checklist covering auth, tokens, and session handling.
COMMON QUESTIONS
SCAN THE FULL AUTH FLOW
A valid token is only half of auth. The VibeEval agent tests the authorization behind it — RLS, object access, and API behavior.