A JSON Web Token (JWT) is a compact, URL-safe way to pass a set of claims, such as “this is user 42 and they’re an editor”, between systems. The token is signed, so the receiver can check that it hasn’t been changed. Most APIs and identity providers (OAuth 2.0, OpenID Connect) use them.
Inspect a token now: the JWT decoder shows the header and claims, converts times to dates, flags common problems and can verify the signature. It runs entirely in your browser.
The three parts
A signed JWT is three Base64URL strings separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiI0MiIsInJvbGUiOiJlZGl0b3IifQ . 3q2-7w…
header payload signature
- Header: which algorithm signed the token, e.g.
{"alg":"HS256","typ":"JWT"}, and sometimes a key ID (kid). - Payload: the claims, e.g.
{"sub":"42","role":"editor","exp":1767225600}. - Signature: computed over
header.payloadwith a secret or private key.
Base64URL is ordinary Base64 with - and _ instead of + and /, and no = padding. You can decode any part with the Base64 decoder. That’s the point to remember: the payload is readable by anyone who has the token.
Registered claims
These have defined meanings (RFC 7519 §4.1):
| Claim | Meaning | Check on the server |
|---|---|---|
iss | Issuer: who created the token | Matches the issuer you trust |
sub | Subject: who it’s about (often a user ID) | Exists in your system |
aud | Audience: who it’s for | Includes your API |
exp | Expiry time | Now is before exp |
nbf | Not valid before | Now is after nbf |
iat | Issued at | Plausible, not in the future |
jti | Unique token ID | Not reused, if you track IDs |
Times are NumericDates: seconds since 1970-01-01 UTC, not milliseconds. Writing exp in milliseconds is a classic bug: the token then appears valid for thousands of years. The Unix timestamp converter and the JWT decoder both show these as readable dates.
Decoding is not verifying
Decoding shows what a token claims. Anyone can write a header and payload, encode them and stick them together. Only the signature proves who issued the token:
- HS256/384/512 (HMAC): the issuer and verifier share one secret. Anyone who can verify can also sign, so keep the secret on servers only. Use a random secret at least as long as the hash: 32 bytes for HS256.
- RS256, PS256 (RSA), ES256 (ECDSA), EdDSA: the issuer signs with a private key, and anyone can verify with the public key, often published as a JWKS at a well-known URL.
The JWT decoder can verify all of these with your browser’s Web Crypto API: paste the secret, or the public key as PEM or JWK. A verified signature tells you the token is authentic. It doesn’t tell you whether your application should accept it. That’s the server’s job.
How servers should validate a token
Use a maintained library, and make sure it does all of this (see RFC 8725, JWT Best Current Practices):
- Pin the algorithm. Accept only the algorithm you expect for that key. Never let the token’s header choose. This blocks
alg: nonetokens, and “algorithm confusion”, where an RS256 public key is misused as an HS256 secret. - Verify the signature with the right key, choosing by
kidonly from keys you already trust. - Check
expandnbf, allowing a small clock skew (a minute or so). - Check
issandaud, so a token issued for another service isn’t accepted by yours. - Then apply your own rules, such as roles, scopes and whether the user still exists.
Common mistakes
- Putting sensitive data in the payload. It’s only encoded. Use opaque IDs, and look the details up on the server.
- Long-lived tokens. A stolen token works until it expires. Keep access tokens short-lived and use revocable refresh tokens.
- Treating “decoded fine” as “valid”. Client-side code can read claims for display, but authorisation decisions belong on the server, after verification.
- Logging tokens. Access logs, error trackers and support tickets are a common source of leaks. Redact the
Authorizationheader. - Weak HMAC secrets. Short or guessable secrets can be brute-forced offline from a single token.
Where to store tokens in the browser
There’s no perfect answer. HttpOnly, Secure, SameSite cookies can’t be read by JavaScript, which limits the damage from XSS, but you need CSRF protection. localStorage is simple, but any script on the page can read it. Many teams use cookies for refresh tokens and keep short-lived access tokens in memory. Follow your framework’s guidance, and fix XSS first: whichever storage you pick, injected scripts can act as the user.
This guide is general technical information. Security requirements depend on your system, so review them with your team or a security professional.