JWT Structure Explained
A JWT consists of three Base64URL-encoded parts:
1. Header
``json
{
"alg": "HS256",
"typ": "JWT"
}
`
Specifies the signing algorithm and token type.2. Payload
`json
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
`
Contains the claims (statements about the user and metadata).3. Signature
Created by signing the header and payload with a secret key.Common JWT Claims
Claim Name Description iss Issuer Who issued the token sub Subject Who the token represents aud Audience Intended recipient exp Expiration When the token expires iat Issued At When the token was created nbf Not Before Token not valid before this time
Three Parts, One Dot-Separated String
A JSON Web Token is three Base64url segments joined by dots:
`
header.payload.signature
`
The header names the algorithm and, usually, a key id. The payload carries the claims. The
signature covers the first two parts and nothing else.
The first two segments are encoded, not encrypted. Anyone holding a token can read every
claim in it without the key — which is the single most important fact about the format, and
the reason a JWT must never carry anything you would not put in a log file.
Decoding Is Not Verifying
Reading a token tells you what it says. It tells you nothing about whether it is genuine.
Those are separate operations, and conflating them is the classic JWT vulnerability:
1. Decode — split on dots, Base64url-decode. No key involved, always succeeds on
well-formed input.
2. Verify — recompute the signature with the key and compare; then check exp,
nbf, iss and aud.
A library function called decode almost never verifies. If your code path calls it and
then trusts the claims, an attacker can hand you any payload they like.
The alg: none Attack
The header states the algorithm, and the header is attacker-controlled. A verifier that
trusts it accepts {"alg":"none"} with an empty signature and validates a forged token.
The related attack swaps RS256 for HS256: the verifier, told the token is symmetric,
uses the *public* key as an HMAC secret — and the public key is public.
Always specify the expected algorithm in the verifier. Never read it from the token.
Standard Claims
| Claim | Meaning | Notes |
|---|---|---|
iss | Issuer | Verify it |
sub | Subject | The user or entity |
aud | Audience | Verify it; a token for another service is not for you |
exp | Expiry | Seconds since epoch, not milliseconds |
nbf | Not before | For tokens issued ahead of use |
iat | Issued at | Useful for age policies |
jti | Token ID | For replay prevention and revocation lists |
exp and iat are seconds. Passing a JavaScript Date.now() value produces a token
that expires in the year 55,000, and that mistake is invisible until someone looks.Which Tool to Use
[Decode](/dev/jwt-decoder/jwt-parser) or [debug](/dev/jwt-decoder/jwt-debugger) a
token, including an expired one.
- [Header](/dev/jwt-decoder/jwt-header-decoder) and
[payload](/dev/jwt-decoder/jwt-payload-decoder) on their own.
- [OAuth access token](/dev/jwt-decoder/oauth-token-decoder) versus
[OIDC ID token](/dev/jwt-decoder/id-token-decoder) — different claim sets for different
jobs, which is the distinction most worth understanding.
- [Verify a signature](/dev/jwt-decoder/jwt-validator),
[check expiry](/dev/jwt-decoder/jwt-expiry-checker), or
[generate a token](/dev/jwt-decoder/jwt-generator) for testing.Access Tokens and ID Tokens Are Not Interchangeable
An OAuth access token authorises a call: it carries scopes and an audience, and it is
meant for an API. An OIDC ID token describes a login: it carries identity claims and a
nonce, and it is meant for the client that requested it.
Sending an ID token to an API as a bearer credential is a common and serious mistake — the
API has no way to know what the user consented to.
Revocation Is the Hard Part
A JWT is valid until it expires, because verification is offline by design. There is no
"log out everywhere" without adding state: a denylist keyed on jti`, a token-version claim
checked against the user record, or short-lived access tokens paired with revocable refresh
tokens. Short expiry plus refresh is the usual answer, and it is a design decision to make
before launch rather than after an incident.
Nothing Leaves Your Browser
Tokens are decoded and verified locally with the Web Crypto API. A token is a live credential, and pasting one into a server-side decoder hands it to whoever runs that server.