How a JWT is structured
A JSON Web Token has three Base64url-encoded segments separated by dots: header.payload.signature. The header and payload contain JSON with Base64 padding omitted. Anyone who has the token can decode those two segments without a key.
A signed JWT is not encrypted. Base64url encoding does not hide the payload. A client or logging system that receives the token can read any user IDs, email addresses, roles or other data stored in it.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← header { "alg": "HS256", "typ": "JWT" }
.
eyJzdWIiOiJ1c2VyXzk5ODIiLCJleHAiOjE3Njd9 ← payload { "sub": "user_9982", "exp": ... }
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQ ← signature over the first two segments
Registered claims
| Claim | Name | What it does |
|---|---|---|
iss | Issuer | Identifies who issued the token. Reject values your API does not trust. |
sub | Subject | Identifies the token’s subject, often with a stable opaque user ID. |
aud | Audience | Identifies the intended recipient. Validate it to prevent use against a different service. |
exp | Expiry | Gives the Unix time after which the token must be rejected. |
nbf | Not before | Gives the Unix time before which the token is not valid. |
iat | Issued at | Records when the token was issued and helps identify unexpectedly old tokens. |
jti | Token ID | Provides a unique identifier that can be used in a revocation list. |
JWT timestamps use seconds since the Unix epoch, not milliseconds. Using milliseconds shifts the date by a factor of 1,000.
Checking a token after a 401 response
Use the decoded header and payload to check these common causes of token rejection.
- Expired token. Check
expand the status panel. Allow for clock differences between the issuer and verifier; many libraries support a small tolerance such as 30–60 seconds. - Wrong audience. Compare
audwith the audience configured by the receiving service, especially when services share an identity provider. - Wrong issuer. Compare
isswith the expected issuer for the environment. A staging token should not be accepted by production. - Missing scopes or roles. Inspect
scope,permissionsand custom role claims. A 403 often means authentication succeeded but the token lacks permission. - Wrong signing key. Confirm that the
kidin the header identifies a key the server currently has, including after key rotation.
Security checks
Never accept alg: none
The JWT specification defines an unsigned mode. If a verifier accepts the algorithm named by the token instead of enforcing its own configuration, it may accept alg: none with no signature. Configure the expected algorithm on the server. The decoder flags none as a warning.
The RS256-to-HS256 confusion attack
A verifier that trusts the header’s algorithm can be tricked into confusing asymmetric and symmetric verification, because an attacker who changes RS256 to HS256 can then sign a forged token using your public RSA key as the HMAC secret, and the verifier will happily accept it. Pin the algorithm in your own configuration. Never read it from the token.
Keep lifetimes short
A stateless access token usually remains usable until the moment it expires, because nothing consults a server-side store that could revoke it early, which means the lifetime you choose is also the size of the window an attacker gets with a stolen copy. Keep it short. Use a refresh-token flow for anything longer.
Storage
JavaScript on the page can read anything in localStorage, so a single XSS bug exposes every token stored there, whereas an HttpOnly, Secure, SameSite=Strict cookie is unreachable from script entirely but shifts the burden onto CSRF protection instead. Neither option is free. Pick the risk you are better equipped to handle.
Verifying a JWT on the server
Applications must verify tokens in code. This Node example uses the jose library:
import { jwtVerify, createRemoteJWKSet } from 'jose';
const jwks = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));
const { payload } = await jwtVerify(token, jwks, {
issuer: 'https://auth.example.com', // Require this issuer
audience: 'api.example.com', // Require this audience
algorithms: ['RS256'], // Allow only this algorithm
clockTolerance: 30, // Allow 30 seconds of clock skew
});
Each constraint is part of verification. The algorithms option prevents algorithm confusion, while issuer and audience reject tokens created for a different authority or service.
Frequently asked questions
Is my token sent to your server?
No. Decoding uses the browser’s atob and TextDecoder APIs, and this tool does not send the token to a server. A live production token is still a credential, so use an expired or test token when possible.
Why can I read the payload without a key?
Base64url is an encoding, not encryption. A valid signature can prove that the signed content was not altered, but it does not hide that content. Do not put secrets in a signed JWT; use JWE when encrypted token content is required.
Can this tool verify the signature?
No. Verification requires the expected signing secret or public key. Keep that material in your application and verify the token on the server with a maintained library.
My token looks valid but the API still returns 401. What now?
Compare aud and iss with the service configuration, confirm that kid matches a current verification key, and compare the issuer and server clocks. A clock difference can make nbf or exp fail even when the token appears current on your machine.
What is the difference between a JWT and a session cookie?
A session cookie can point to server-side state that you revoke by deleting the session. A self-contained JWT can be checked without a session lookup, but early revocation requires a denylist or another server-side check. Short access-token lifetimes limit that tradeoff.