Özer Cem KELAHMETKELAHMET
Tools · Security

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:

  1. Parse the header and reject any algorithm not on its allow-list, including none.
  2. Find the key by kid from the trusted issuer's JWKS, never from a key embedded in the token.
  3. Verify the signature.
  4. Check iss equals the expected issuer and aud includes this service.
  5. Check exp and nbf against the current time, with a small clock-skew allowance.
  6. 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

  1. Use a well-maintained library and keep it updated; check it against OWASP guidance on authentication.
  2. Allow-list expected algorithms per issuer; never accept none.
  3. Always verify issuer, audience, expiry and not-before.
  4. Prefer asymmetric algorithms, such as ES256 or RS256, when several services verify tokens.
  5. Fetch keys from the trusted issuer's JWKS and support key rotation by kid.
  6. Keep access token lifetimes short, typically minutes, and use refresh tokens for longevity.
  7. Do not put secrets or unnecessary personal data in the payload.
  8. Use separate audiences for separate APIs, so a token for one cannot be used at another.
  9. Follow RFC 8725 best practices for any custom use.

Common mistakes

  • Decoding a token and trusting its claims without verifying the signature.
  • Trusting the alg value 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.

It belongs to Security and governance in my stack, under MLOps and AI infrastructure. Others in the same group:

Type to search.
    ↑↓ navigate↵ open