JSON Web Tokens explained: claims, signatures and safe validation
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:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9Header: {"alg":"HS256","typ":"JWT"} — the signing algorithm and the token type.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJ1c2VyLTQ4MjEiLCJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImlhdCI6MTc5MTk2ODQwMCwiZXhwIjoxNzkxOTY5MzAwLCJqdGkiOiJhOTFjMmU3ZiJ9Payload: issuer https://auth.example.com, subject user-4821, audience https://api.example.com, issued at 1791968400, expires at 1791969300, ID a91c2e7f.yQ_-tR1KNYT8HL6voHLYDvp5GY6EWmR33VMZ8BbD1usSignature: an HMAC-SHA256 of the first two parts, made with a secret key. It is binary data, not JSON.
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.
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.
| Claim | Name | Meaning |
|---|---|---|
iss | Issuer | Who created and signed the token, usually a URL |
sub | Subject | Who the token is about, usually a user ID |
aud | Audience | Who the token is for; a recipient not listed must reject it |
exp | Expiration time | From this moment on, the token must not be accepted |
nbf | Not before | Before this moment the token must not be accepted |
iat | Issued at | When the token was created, which shows its age |
jti | JWT ID | A 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.
| alg | Method | Who can check it | Notes |
|---|---|---|---|
HS256 | HMAC with SHA-256 and a shared secret | Anyone with the secret — who can then also create tokens | The key must be at least 256 bits (RFC 7518) and never a password (RFC 8725). |
RS256 | RSA signature with SHA-256 | Anyone with the public key | RSA keys of at least 2048 bits. The suggested default for OpenID Connect ID tokens. |
ES256 | ECDSA on the P-256 curve with SHA-256 | Anyone with the public key | Much shorter keys and signatures than RSA. |
Ed25519 | EdDSA with the Ed25519 parameter set | Anyone with the public key | Registered in October 2025 (RFC 9864), replacing the older, less specific identifier EdDSA, which many libraries still use. |
none | No signature | Nobody: the token is unprotected | Implementations 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:
- 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.
- Verify the signature with the key that belongs to the expected issuer.
- Check that iss is the issuer you trust and that aud names your service.
- 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.
- 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.
- 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:
- alg none: the attacker changes the header to none and deletes the signature. A server that trusts the header accepts a token anyone could have written.
- Key confusion: a server expects RS256, but the attacker sends a token marked HS256, signed with the server's own public key as if it were an HMAC secret. A library that picks the algorithm from the header then checks it with that same public key — and it passes.
- Weak secrets: an HS256 secret that is short or a password can be found by guessing offline, from a single captured token, without any contact with the server. With the secret, the attacker can sign any token.
- Substitution: a valid token issued for one service, or for one purpose, is presented to another — an ID token used as an access token, or a token for one API used at a different one.
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
- RFC 7519 — JSON Web Token (JWT) (May 2015)
- RFC 7515 — JSON Web Signature (JWS) (May 2015)
- RFC 7516 — JSON Web Encryption (JWE) (May 2015)
- RFC 7518 — JSON Web Algorithms (JWA) (May 2015)
- RFC 8725 — JSON Web Token Best Current Practices (February 2020)
- RFC 9068 — JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens (October 2021)
- RFC 9700 — Best Current Practice for OAuth 2.0 Security (January 2025)
- RFC 9864 — Fully-Specified Algorithms for JOSE and COSE (October 2025)
- OpenID Connect Core 1.0 incorporating errata set 2 (December 2023)
- OWASP — JSON Web Token Cheat Sheet
- OWASP — HTML5 Security Cheat Sheet: Local Storage
Related guides
- IP addresses explained: IPv4, IPv6 and the reserved ranges
- Subnets and CIDR explained: prefixes, masks and usable hosts
- DNS records explained: A, AAAA, CNAME, MX, TXT and NS
- Is a website down, or is it just you? How to check, step by step
- HTTP status codes and Cloudflare errors: what 403, 404, 429, 500, 503 and 521 mean
- Unix timestamps and time zones explained
- Counting dates: days between dates, leap years and ISO weeks
- Image formats explained: JPEG, PNG, WebP, AVIF and HEIC
- Password strength explained: length, entropy and passphrases
- Hash functions explained: SHA-256, checksums and password hashing
- UUIDs explained: v4, v7 and choosing the right version
- QR codes explained: capacity, error correction and print size
- Percentages and VAT explained: adding, removing and stacking
- Common JSON errors and how to fix them
- Editing PDFs in the browser: what merging, splitting and converting keep