JSON Web Tokens (JWT)
A JSON Web Token is a compact, signed token carrying claims such as user id and expiry. Learn its structure, validation and common security mistakes.
Overview
A JSON Web Token, or JWT, is a compact, URL-safe string that carries a set of statements, called claims, such as "this is user 42", "issued by our login service", "valid until 10:15". In its common signed form the token is protected by a digital signature or a message authentication code, so anyone who holds the right key can check that the claims were issued by a trusted party and have not been changed. The receiver can verify the token locally, without calling a central server.
An everyday analogy is a festival wristband with a tamper-proof seal. Staff at the gate check your ticket once and fit a band printed with your access level and the date. After that, every bar and stage checks the band at a glance; they do not phone the ticket office. If someone tries to alter the printing, the seal breaks. But anyone who obtains your band can use it, and it keeps working until the festival ends unless security is told to watch for it. JWTs share both the convenience and the risks.
JWTs are used as OAuth 2.0 access tokens, as OpenID Connect ID tokens, for service-to-service authentication, and for signed assertions such as client authentication and workload identity. In AI systems they often carry a user's identity and permissions from a gateway to model, retrieval and tool services, so each can enforce access without a database lookup, which fits a zero trust approach where every hop verifies the caller.
What it is
A signed JWT, technically a JWS in compact serialisation, has three parts separated by dots: header.payload.signature. Each part is Base64url-encoded.
| Part | Contents | Example |
|---|---|---|
| Header | Token type and algorithm, and often a key ID | {"alg":"RS256","kid":"2026-10","typ":"JWT"} |
| Payload | Claims about the subject and the token | {"iss":"https://login.example","sub":"u42","aud":"api","exp":1793000000} |
| Signature | Signature or MAC over the encoded header and payload | Bytes computed with the issuer's key |
Registered claims defined in the standard include:
| Claim | Meaning |
|---|---|
iss |
Issuer: who created the token |
sub |
Subject: whom the token is about |
aud |
Audience: who the token is for |
exp |
Expiry time, in seconds since the Unix epoch |
nbf |
Not valid before this time |
iat |
Issued-at time |
jti |
Unique token ID, useful for replay detection |
Applications add their own claims, such as scope or roles. Important: the payload of a signed JWT is encoded, not encrypted. Anyone holding the token can read it. Encrypted tokens use JWE, a separate format.
Where it came from
JWT was developed in the IETF's JOSE (JSON Object Signing and Encryption) work as a simpler, JSON-based alternative to XML security tokens such as SAML assertions. The JWT specification was published as RFC 7519 in May 2015, alongside the related standards: JSON Web Signature (JWS, RFC 7515), JSON Web Encryption (JWE, RFC 7516), JSON Web Key (JWK, RFC 7517) and JSON Web Algorithms (JWA, RFC 7518). The RFC suggests pronouncing JWT as "jot".
Real-world vulnerabilities in libraries, such as accepting the none algorithm or confusing key types, led to RFC 8725, "JSON Web Token Best Current Practices", in February 2020. RFC 9068 (2021) defined a standard JWT profile for OAuth 2.0 access tokens.
How it works
1. Issue
An issuer, such as an authorisation server, builds a header and a set of claims, encodes both with Base64url, and computes a signature over header.payload using its key. Common algorithms are RS256 (RSA with SHA-256), ES256 (ECDSA with P-256) and EdDSA for asymmetric signatures, and HS256 (HMAC with SHA-256) for shared secrets.
2. Publish keys
With asymmetric algorithms, the issuer publishes its public keys as a JSON Web Key Set (JWKS) at a known URL. Each key has a kid. Verifiers fetch and cache the set.
3. Transmit
The client sends the token, usually in an Authorization: Bearer header, over TLS.
4. Verify
The receiver performs strict checks, in this order:
- Parse the header and reject any algorithm not on its allow-list, including
none. - Find the key by
kidfrom the trusted issuer's JWKS, never from a key embedded in the token. - Verify the signature.
- Check
issequals the expected issuer andaudincludes this service. - Check
expandnbfagainst the current time, with a small clock-skew allowance. - Check application claims, such as required scopes.
Only after all checks pass are the claims trusted.
5. Expire or revoke
JWTs are usually valid until they expire, because verification is local. Short lifetimes limit risk. Where earlier revocation is needed, services can check a deny-list of jti values or use token introspection.
A minimal verification in Python with the PyJWT library:
import jwt
jwks = jwt.PyJWKClient("https://login.example/.well-known/jwks.json")
token = request.headers["Authorization"].removeprefix("Bearer ")
key = jwks.get_signing_key_from_jwt(token)
claims = jwt.decode(token, key.key, algorithms=["RS256"],
audience="scoring-api", issuer="https://login.example")
Key concepts and terms
| Term | Meaning |
|---|---|
| Claim | A name-value statement in the payload. |
| JWS | JSON Web Signature, the signed form of a JWT. |
| JWE | JSON Web Encryption, the encrypted form. |
| JWK / JWKS | A JSON representation of a key / a set of keys. |
kid |
Key identifier selecting the verification key. |
| Base64url | URL-safe encoding used for each part. |
| Bearer token | A token usable by whoever holds it. |
| Algorithm confusion | Attack tricking a verifier into using the wrong algorithm or key type. |
| Clock skew | Small time differences between servers, allowed during validation. |
| Stateless token | A token verified without server-side session storage. |
| Token introspection | Asking the issuer whether a token is still active. |
A worked example
This scenario is illustrative. A company exposes an AI assistant through an API gateway. Behind it are three services: retrieval, model inference and a ticketing tool. Each must know who the user is and what they may do, but calling the identity provider on every internal hop would add latency.
The identity provider issues JWT access tokens signed with ES256, valid for 10 minutes, with claims iss, sub, aud: "assistant-api", exp, scope: "kb.read tickets.create" and tenant: "t-17". Each service caches the JWKS, refreshing it every hour or when it sees an unknown kid. Verification takes well under a millisecond per request.
The retrieval service filters documents by the tenant claim, so one customer's documents never reach another's prompt. The ticketing tool requires tickets.create; a token without it gets 403. A security review finds that an older internal service accepted any algorithm in the header. A test token with alg: none and no signature passed its checks. The fix is a strict allow-list of ES256 and a library upgrade. The team also stops putting the user's email and phone number in tokens, since payloads are readable by any holder and end up in logs.
Strengths and limitations
Strengths:
- Compact, URL-safe and easy to pass in headers.
- Verifiable locally without a database or network call.
- Standard claims and wide library support in every major language.
- Asymmetric signatures let many services verify while only the issuer signs.
- Flexible: usable for access tokens, ID tokens and signed assertions.
Limitations:
- Hard to revoke before expiry without extra infrastructure.
- Payload is readable by anyone holding a signed token.
- Many security pitfalls in validation: algorithms, keys, audience.
- Tokens grow with claims and can exceed header size limits.
- Bearer tokens can be replayed if stolen unless bound to a key.
How it compares with alternatives
| Option | Description | When it fits better |
|---|---|---|
| Opaque tokens with introspection | Random strings checked at the issuer | Immediate revocation and keeping claims private |
| Server-side sessions | A session ID cookie mapped to server state | Single web applications with a session store |
| PASETO | A token format with fixed, safer algorithm choices | Teams wanting fewer configuration pitfalls, where supported |
| SAML assertions | XML-signed security tokens | Enterprise federation using SAML |
| mTLS | Certificate-based identity at the connection | Strong service identity; can bind JWT access tokens |
Practical guidance and best practices
- Use a well-maintained library and keep it updated; check it against OWASP guidance on authentication.
- Allow-list expected algorithms per issuer; never accept
none. - Always verify issuer, audience, expiry and not-before.
- Prefer asymmetric algorithms, such as ES256 or RS256, when several services verify tokens.
- Fetch keys from the trusted issuer's JWKS and support key rotation by
kid. - Keep access token lifetimes short, typically minutes, and use refresh tokens for longevity.
- Do not put secrets or unnecessary personal data in the payload.
- Use separate audiences for separate APIs, so a token for one cannot be used at another.
- Follow RFC 8725 best practices for any custom use.
Common mistakes
- Decoding a token and trusting its claims without verifying the signature.
- Trusting the
algvalue from the header, enabling algorithm confusion. - Using a weak HMAC secret that can be brute-forced.
- Issuing tokens valid for days or weeks with no revocation plan.
- Storing tokens in browser local storage where injected scripts can read them.
- Treating a JWT as encrypted and placing sensitive data in it.
Frequently asked questions
Is a JWT encrypted?
Usually not. A signed JWT (JWS) protects integrity but its payload is only Base64url-encoded and readable by anyone. Use JWE if you need confidentiality, or keep sensitive data out of the token.
How do I revoke a JWT?
Keep lifetimes short so tokens expire quickly. For earlier revocation, maintain a deny-list of token IDs, check a session version claim, or use opaque tokens with introspection for sensitive operations.
Which algorithm should I use?
For tokens verified by many parties, an asymmetric algorithm such as ES256, RS256 or EdDSA. HS256 is acceptable when issuer and verifier are the same trusted system and the secret is strong.
Are OAuth access tokens always JWTs?
No. They can be opaque. RFC 9068 defines a standard JWT format for access tokens when an authorisation server chooses to use one.
What is the difference between an ID token and an access token?
Both may be JWTs. An ID token tells the client application who signed in; an access token authorises calls to an API. Each must be used only by its intended audience.
Where should a browser store a JWT?
Ideally it should not handle long-lived tokens at all. Many architectures keep tokens on a back end and give the browser an HttpOnly, Secure cookie session.
Glossary
- JWT: JSON Web Token.
- Claim: a statement inside the token.
- Header: metadata naming the algorithm and key.
- Payload: the claims section.
- Signature: proof of integrity and origin.
- JWS: the signed token format.
- JWE: the encrypted token format.
- JWKS: a published set of public keys.
exp: the expiry claim.aud: the audience claim.
Summary
A JWT is a compact token of claims protected by a signature, defined in RFC 7519 in 2015 as part of the JOSE standards. It lets services verify identity and permissions locally, which makes it popular for OAuth access tokens, OpenID Connect ID tokens and service authentication. Its convenience comes with risks: readable payloads, difficult revocation and many validation pitfalls. Use a maintained library, allow-list algorithms, verify issuer, audience and time claims, prefer asymmetric keys with rotation, keep lifetimes short and keep sensitive data out of the payload.
Related
It belongs to Security and governance in my stack, under MLOps and AI infrastructure. Others in the same group: