What's inside a token
A JWT is three Base64url strings joined by dots: header.payload.signature. The header says how it was signed, the payload carries the claims, and the signature lets a server prove the token hasn't been tampered with. The registered claims you'll see most:
| Claim | Meaning | Claim | Meaning |
|---|---|---|---|
iss | Issuer | exp | Expiry time |
sub | Subject (user) | iat | Issued at |
aud | Audience | nbf | Not valid before |
Gotcha — decoding is not verifying. This tool reads the token; it does not check the signature. A decoded payload could be forged. Always verify the signature on your server with the secret or public key before trusting any claim — and never store passwords or secrets in a payload, because anyone can read it.
How a server actually verifies a token
With a symmetric algorithm like HS256, the server recomputes the signature over header.payload using a shared secret and checks it matches. With an asymmetric algorithm like RS256, the token is signed with a private key and verified with the matching public key — so clients can verify a token without being able to mint one. Either way, an expired token (exp in the past) must be rejected even when the signature is valid.
Reject alg: none. A token claiming no signing algorithm is a classic forgery attempt. A correct verifier must refuse it rather than treat an unsigned token as valid.
Where a JWT fits in a login
A user signs in; the server checks the password once and returns a signed JWT. After that the client sends the token with each request (usually in an Authorization: Bearer … header) instead of re-sending the password. The server verifies the signature and exp on every request — a fast, stateless check — and reads the claims to know who is calling. Tokens are kept short-lived, with a separate refresh token used to obtain a new one.
Why there are usually two tokens
JWTs are stateless, which is their best feature and their biggest constraint. The server doesn't store a session, so it can verify a token without a database lookup — but that also means it can't easily revoke one. A leaked token stays valid until it expires. The standard answer is two tokens with different lifetimes: a short-lived access token (minutes) that you send with every request, and a longer-lived refresh token (days or weeks) kept somewhere safer and used only to mint new access tokens. If an access token leaks, it dies on its own quickly; if you need to cut someone off, you revoke the refresh token and they're locked out at the next refresh. Some setups also rotate the refresh token on each use so a stolen one can be detected.
Where you store the token matters as much as how it's signed. Keep it in localStorage and any cross-site-scripting bug on your page can read it and walk away with the session. Put it in an HttpOnly cookie and JavaScript can't touch it, which shuts that door but opens a CSRF concern you handle separately. There's no free option; pick the trade-off deliberately rather than defaulting to localStorage because it's easy.
The classic ways JWT verification gets broken
Most JWT vulnerabilities aren't fancy maths — they're verifiers being too trusting. The alg: none trick sends a token with the signature stripped and the algorithm set to "none", hoping the server accepts an unsigned token; a correct verifier refuses outright. The algorithm-confusion attack is sneakier: a server expecting an RS256 token (verified with a public key) is tricked into treating the token as HS256 and verifying it with that public key as if it were an HMAC secret — and the public key isn't secret. The defence is the same in both cases: pin the expected algorithm in your verifier instead of trusting whatever the token's header claims, and always check exp and aud rather than just confirming the signature parses.
Should I store a JWT in localStorage or a cookie?
Each has a downside. localStorage is readable by any XSS on your page; an HttpOnly cookie hides the token from JavaScript but needs CSRF protection. Many apps use an HttpOnly cookie for the refresh token and keep the short-lived access token in memory.
What is the alg: none attack?
A forged token that sets the algorithm to "none" and drops the signature, hoping the server treats it as valid anyway. A correct verifier rejects unsigned tokens and only accepts the specific algorithm it expects, never whatever the token's header asks for.
Is a JWT encrypted?
No. A standard JWT is signed, not encrypted. The header and payload are Base64url-encoded and fully readable, so treat everything in the payload as public.
Is my token sent anywhere?
No. The token is split and Base64url-decoded in your browser. Nothing is transmitted — safe for tokens you don't want to leak.
Why isn't the signature checked?
Verifying needs the signing secret or public key, which only your backend should hold. Doing it here would mean sending you the key, which defeats the purpose. Verification belongs on the server.
What does "expired" mean here?
If the exp claim is in the past, the token would be rejected by a correct server. The tool flags it, but the real decision always happens server-side.