JSON Web Tokens explained: claims, signatures and safe validation

Updated:

A JSON Web Token, or JWT, is a short piece of text that carries a few facts about a user or a request — who issued it, who it is about, when it expires — together with a signature that lets the recipient check that nobody has changed them. APIs accept JWTs as access tokens, sign-in systems issue them as ID tokens, and many web apps use them as session tokens. This guide explains what is inside one, how the signing algorithms differ, what a server has to check before trusting a token, the attacks that exploit skipped checks, and how to handle expiry and storage.

What a JWT is

The format is defined in RFC 7519, published in 2015. A JWT is a set of claims — statements such as “the subject is user-4821” — encoded as JSON and then either signed or encrypted. Almost every JWT in use is signed, as a JSON Web Signature (JWS, RFC 7515): anyone can read it, but nobody without the key can change it unnoticed. Encrypted JWTs (JWE, RFC 7516) also hide the contents and are much less common.

Two standards define the JWTs you are most likely to meet. OpenID Connect, the sign-in layer used by “Sign in with…” buttons, issues an ID token that must be a signed JWT. The JWT profile for OAuth 2.0 access tokens (RFC 9068, 2021) defines how an API access token looks when it is a JWT, although many providers' access tokens predate it and do not follow it exactly.

The three parts

A signed JWT is three pieces of Base64URL text joined by dots: a header, a payload and a signature. Here is a complete token, split at the dots:

Base64URL is ordinary Base64 with two characters replaced so that the result is safe in URLs (- and _ instead of + and /), and in JWTs the = padding at the end is dropped. It is an encoding, not encryption: anyone can turn the first two parts back into JSON.

The signature is computed over the exact text of the encoded header and payload, joined by the dot between them. Change one character of either — make the expiry later, the subject someone else — and the signature no longer matches.

Open the JWT decoder

The standard claims

RFC 7519 defines seven registered claims. All are optional in the base standard; the protocols built on it make some of them mandatory.

ClaimNameMeaning
issIssuerWho created and signed the token, usually a URL
subSubjectWho the token is about, usually a user ID
audAudienceWho the token is for; a recipient not listed must reject it
expExpiration timeFrom this moment on, the token must not be accepted
nbfNot beforeBefore this moment the token must not be accepted
iatIssued atWhen the token was created, which shows its age
jtiJWT IDA unique identifier, useful against replay and for revocation

The three time claims (exp, nbf and iat) are Unix timestamps: numbers of seconds, normally whole, since 1 January 1970 UTC. In the example, 1791968400 is 14 October 2026 at 09:00 UTC and 1791969300 is fifteen minutes later. RFC 7519 lets a server allow a little leeway for clocks that disagree, “usually no more than a few minutes”.

An OpenID Connect ID token must contain iss, sub, aud, exp and iat. An access token under RFC 9068 must contain iss, exp, aud, sub, client_id, iat and jti, and carry the type at+jwt in its header. Everything else in a payload — a name, an e-mail address, roles — is defined by the application.

Signing algorithms

The alg value in the header names the algorithm. They fall into two groups: a shared secret, where the same key signs and checks, and a key pair, where a private key signs and a public key checks.

algMethodWho can check itNotes
HS256HMAC with SHA-256 and a shared secretAnyone with the secret — who can then also create tokensThe key must be at least 256 bits (RFC 7518) and never a password (RFC 8725).
RS256RSA signature with SHA-256Anyone with the public keyRSA keys of at least 2048 bits. The suggested default for OpenID Connect ID tokens.
ES256ECDSA on the P-256 curve with SHA-256Anyone with the public keyMuch shorter keys and signatures than RSA.
Ed25519EdDSA with the Ed25519 parameter setAnyone with the public keyRegistered in October 2025 (RFC 9864), replacing the older, less specific identifier EdDSA, which many libraries still use.
noneNo signatureNobody: the token is unprotectedImplementations must not accept it by default (RFC 7518).

HS256 suits a single service that both issues and checks its own tokens. When other services need to check tokens — several APIs, or partners — a key pair is the better fit: they receive only the public key, which cannot be used to create tokens.

Decoding is not verifying

Anyone can write a JWT with any payload; decoding it, as the nTools decoder does, only shows what it claims. A token proves something only after the server that receives it has made these checks, which follow RFC 7519 and RFC 8725, the IETF's best current practices for JWTs:

  1. Fix the accepted algorithms in the server's configuration and reject everything else. Never let the alg in the token's header decide how it is checked, and use each key with one algorithm only.
  2. Verify the signature with the key that belongs to the expected issuer.
  3. Check that iss is the issuer you trust and that aud names your service.
  4. Require exp and the other claims your service depends on to be present, and check exp and nbf against the current time with at most a few minutes of leeway. A token without exp would otherwise never expire.
  5. Where a token type is defined, check typ — for example at+jwt for access tokens — so that one kind of token cannot be passed off as another.
  6. Treat the header fields kid, jku and x5u as untrusted input: look kid up in your own list of keys, and take keys only from the issuer's configured key set or from addresses you have allowed in advance.

In practice these checks belong to a maintained JWT library configured with the expected issuer, audience and algorithms, not to code written by hand.

The classic attacks

Most JWT vulnerabilities are not weaknesses in the cryptography but checks that a server skipped. RFC 8725 describes these:

The checks in the previous section close each of them: a fixed list of algorithms, with each key tied to a single algorithm, stops the first two, a random secret of at least 256 bits the third, and checking aud and typ the last.

The payload is public

A signature protects against changes, not against reading. Tokens travel in request headers (and sometimes, unwisely, in URLs) and end up in logs, browser storage and Referer headers, and anyone who sees one can decode it. Passwords, secrets and personal data that the recipient does not need should not be in a payload.

When claims have to travel with the token and stay confidential, the token can be encrypted as a JWE, which has five parts instead of three. OWASP's suggested alternative is often simpler: keep the data on the server and give the client an opaque reference token that means nothing on its own.

Expiry, revocation and storage

A signed JWT is valid until exp, wherever it is: the server does not need to look it up, which is what makes JWTs fast to check, and also why a stolen token keeps working. Short expiry times limit the damage. To withdraw a token earlier, the server has to keep some state after all — OWASP describes a denylist keyed on the jti and iss claims; an entry can be dropped once the token's exp has passed.

Applications that need longer sessions pair a short-lived access token with a refresh token used to get new ones. The OAuth 2.0 security best practices (RFC 9700, January 2025) require refresh tokens issued to public clients — typically browser and mobile apps, which cannot keep a secret — to be either sender-constrained, that is cryptographically bound to that client, or rotated: replaced by a new one each time they are used, with the old one invalidated.

In the browser, OWASP advises against keeping session identifiers in localStorage, because a single cross-site scripting flaw can read everything stored there. A cookie marked HttpOnly cannot be read by JavaScript at all, though it should also be Secure and SameSite, and a script injected into the page can still send requests that carry it.

Frequently asked questions

Is a JWT encrypted?

Usually not. Most JWTs are signed, and their header and payload are only Base64URL-encoded, so anyone who has the token can read them. Only a JWE, with five parts, hides its contents.

Can I read a JWT without the key?

Yes, unless it is an encrypted JWE. For a signed JWT the key is needed to create or verify the signature, not to read the header and payload.

How long should a JWT be valid?

The standards set no single figure. The usual advice is as short as the application allows, with refresh tokens for longer sessions; a lost token is then only useful for a short time.

Should I use JWTs for website sessions?

Not necessarily. A classic session ID in a cookie can be revoked at once on the server; a JWT saves a lookup on every request but cannot be withdrawn before it expires without keeping extra state.

Is it safe to paste a token into the nTools decoder?

The token is decoded in your browser and not sent anywhere. Even so, prefer an expired or test token: a live token is a credential.

Previous in the series ← Hash functions explained: SHA-256, checksums and password hashing

References

  1. RFC 7519 — JSON Web Token (JWT) (May 2015)
  2. RFC 7515 — JSON Web Signature (JWS) (May 2015)
  3. RFC 7516 — JSON Web Encryption (JWE) (May 2015)
  4. RFC 7518 — JSON Web Algorithms (JWA) (May 2015)
  5. RFC 8725 — JSON Web Token Best Current Practices (February 2020)
  6. RFC 9068 — JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (October 2021)
  7. RFC 9700 — Best Current Practice for OAuth 2.0 Security (January 2025)
  8. RFC 9864 — Fully-Specified Algorithms for JOSE and COSE (October 2025)
  9. OpenID Connect Core 1.0 incorporating errata set 2 (December 2023)
  10. OWASP — JSON Web Token Cheat Sheet
  11. OWASP — HTML5 Security Cheat Sheet: Local Storage

Related guides