What's Actually Inside a JWT: Understanding Common Claims
Decode any JWT and you'll get a JSON object full of short, cryptic field names — iss, aud, sub, and whatever custom fields the issuer added. They're standardized enough that once you know what a handful of them mean, most tokens you'll ever encounter become readable at a glance.
The registered claims (part of the JWT spec)
These have fixed meanings defined by the JWT standard itself — any library or service that produces JWTs is expected to use them the same way:
iss(issuer) — who created and signed the token. Typically a URL or identifier for the authentication service, e.g.https://auth.example.com.sub(subject) — who the token is about, almost always a user ID. This is the "who is this token for" field.aud(audience) — who the token is intended for, i.e. which API or service should accept it. A service should reject a token whoseauddoesn't match itself, even if the signature is otherwise valid — this stops a token issued for one service from being replayed against another.exp(expiration) — a Unix timestamp after which the token should no longer be accepted, regardless of signature validity.iat(issued at) — when the token was created, as a Unix timestamp. Useful for auditing and for calculating a token's actual age.nbf(not before) — a Unix timestamp before which the token isn't valid yet. Less common thanexp, used for tokens issued slightly ahead of when they should take effect.jti(JWT ID) — a unique identifier for this specific token, useful for logging or for maintaining a revocation list of individual tokens.
Common custom claims (not part of the spec, but everywhere in practice)
Beyond the registered claims, issuers add whatever additional fields their application needs. These aren't standardized, but a few show up often enough to be worth recognizing:
roles/permissions/scope— what the token's holder is allowed to do. Naming and structure vary a lot between systems (a flat array of strings, a space-separated scope string, a nested object).email/name— basic profile info included so a service doesn't need a separate lookup just to display who's logged in.org_id/tenant_id— common in multi-tenant systems, identifying which organization or workspace the token applies to.
Reading a real payload, claim by claim
A typical access token payload might look like this:
{
"iss": "https://auth.example.com",
"sub": "user_8f2a91",
"aud": "api.example.com",
"iat": 1735689600,
"exp": 1735693200,
"roles": ["editor"],
"org_id": "org_4471"
}
Read left to right, this says: a token issued by auth.example.com, identifying user user_8f2a91, meant only for api.example.com to accept, created at a specific moment and valid for one hour after that (exp minus iat), granting "editor" permissions within organization org_4471. Every field here is answerable on its own — the skill in reading a JWT quickly is just recognizing each abbreviation without having to look it up every time, which is really all this reference is for.
What you can and can't trust from a decoded token
Every claim listed here is only meaningful if the token's signature has actually been verified against the issuer's real signing key. Decoding shows you what a token claims; it says nothing about whether those claims are genuine. A token with a roles: ["admin"] claim and an invalid or unchecked signature proves nothing — anyone can construct a JSON payload that says whatever they want. This is why signature verification, done server-side with the actual key, is the step that makes any of these claims trustworthy.
Try it
FreeToolDev's JWT Decoder decodes multiple tokens at once and lays out every claim clearly, with human-readable timestamps for exp/iat/nbf — entirely client-side, so nothing you paste in is sent anywhere.