EXPLAINER

JSON Web Tokens

A JWT is a signed JSON payload used as a token. Anyone can read the payload. Only a valid signature, the right issuer and the right audience make it trustworthy. Decoding is not validation.

Short answer: A JWT is a signed token with JSON claims. Decode to inspect it. Validate the signature, issuer, audience and expiry before you trust it. It is a format used by OAuth and OIDC, not a complete security design.

What it is

A JSON Web Token (JWT) is a compact string with three parts separated by dots: a header, a payload, and a signature. The first two are base64url-encoded JSON. The payload holds claims such as subject (sub), expiry (exp), issuer (iss) and audience (aud). The signature is produced with a key so the receiver can detect tampering. JWTs are commonly used as OAuth access tokens and as OIDC ID tokens. Not every access token is a JWT. Not every JWT is a good idea.

The payload is not encrypted in the usual signed JWT (JWS). Anyone who has the string can read the claims. "I put a secret in the JWT" means you published the secret. Encryption (JWE) exists and is a separate, rarer choice.

Why and when it is used

Use a JWT when a standard provider issues one and your libraries know how to validate it, or when you need a token a service can check without a database lookup and you accept that you cannot easily revoke it before expiry. Use opaque session ids stored on the server when you want instant logout and revocation to be trivial. Many apps do both: a JWT from the identity provider, then a server session for the app.

Do not use a JWT as a place to stash the user's entire profile, their permissions for every product, and a copy of their email password. Do not invent a JWT scheme to avoid learning sessions if your only client is a first-party website. Cookies exist.

How it works

The issuer signs the header and payload with a private key (asymmetric, typical for OIDC) or a shared secret (symmetric, typical for tokens you issue yourself). The verifier checks the signature with the public key or the shared secret, then checks claims. Expiry rejects old tokens. Issuer rejects tokens from another authority. Audience rejects tokens minted for a different API. A nonce or a token id can stop replays in specific flows. If you skip any of these, you are not validating.

Libraries matter. A hand-rolled base64 decode that trusts the header's alg field is the historic "none" algorithm bug and the "switch to HMAC with the public key" bug. Pin allowed algorithms. Fetch signing keys from the provider's key set and cache them. Do not accept a key embedded in the token.

Technologies and dependencies

A maintained JWT library in your language, the issuer's keys, and a clock that is roughly correct. OAuth 2.0 and OpenID Connect are the protocols that usually mint the tokens you should accept. Authentication versus Authorization reminds you that a valid token is not a yes to every action. API Keys are a different string with no claims structure.

How to get started

  1. Take an ID token from a real login in a dev tenant. Paste it into a local debugger that does not upload it, or decode the payload offline. Read iss, aud, exp, sub.
  2. Validate it with a library configured for that issuer and your client id as the audience. It should succeed.
  3. Flip one character in the payload and confirm validation fails.
  4. Change the expected audience and confirm validation fails.
  5. If you issue your own tokens, use an asymmetric key, a short expiry, a specific audience per API, and a library. Store the private key as a secret, not in the repo.
  6. Decide how logout works. If you cannot revoke, keep the lifetime short.

Never send a real token to a public "decode JWT" website. Those sites do not need to see your credentials, and some keep what you paste.

Cautions and trade-offs

Long-lived JWTs are bearer secrets. Theft equals access until expiry. Put only non-sensitive claims in them because they are readable and they end up in logs if you are careless. Authorization decisions that must change immediately (disabled user, removed admin) fit poorly in a token that lives for a day unless you add a revocation check, which removes the "stateless" benefit.

Do not accept the none algorithm. Do not confuse decoding with validation in code review. And do not use a JWT to avoid CSRF thinking: if you store it in a cookie, you still need cookie protections; if you store it in localStorage, any XSS can read it. The format is not the security model.