Classic web sessions keep state on the server: the browser holds an opaque session ID, and the server looks it up in a store on every request. That store is a scaling and coordination cost, especially across many services. A JWT inverts the arrangement. It hands the claims themselves to the client, wrapped in a signature, so the server can trust a request by checking the signature instead of a database. The power and the danger both come from that single move.

The idea

A JWT is “a compact, URL-safe means of representing claims to be transferred between two parties.” The claims are “encoded as a JSON object” and either signed (a JWS) or encrypted (a JWE). Because a signed JWT is self-verifying, a server can authenticate a request with no server-side session lookup, statelessly. The catch is that this security holds only if the server actually verifies the signature correctly, and the standard defines a mode, and permits mistakes, where it does not.

Compact by design

JWTs target tight spaces: “space constrained environments such as HTTP Authorization headers and URI query parameters.” The serialization reflects that, three base64url segments joined by dots, header, payload, signature. The header names the algorithm, the payload carries the claims (issuer, expiry, subject, and whatever else), and the signature covers the first two.

The self-contained structure is the whole appeal. A resource server that receives a JWT does not phone home. It recomputes the signature over the header and payload with the issuer’s key and, if it matches, treats the claims as authentic. This is the same offline-verification property that lets an OpenID Connect ID token or a Kerberos ticket be checked without a callback, and it is exactly why JWTs became the default carrier for OAuth access tokens and OIDC identity claims.

alg:none, the mode that removes the lock

Here is the sharp edge. RFC 7519 defines the Unsecured JWT: “JWTs MAY also be created without a signature or encryption.” Such a token “is a JWS using the ‘alg’ Header Parameter value ‘none’ and with the empty string for its JWS Signature value.” A perfectly spec-compliant JWT can therefore carry {"alg":"none"} and no signature at all.

The token’s own header declares which algorithm to verify with. A naive library that trusts the header’s alg field will, when handed alg:none, dutifully “verify” a token that anyone could have forged, because there is nothing to verify. The classic attack is to take a legitimate token, flip its algorithm to none, strip the signature, and edit the claims freely. The defect is not in the format; it is in a verifier that lets the attacker choose the verification algorithm.

A JWT is not trustworthy just because it parsed

The spec is blunt about this: “The contents of a JWT cannot be relied upon in a trust decision unless its contents have been cryptographically secured and bound to the context necessary for the trust decision.” A decoded, well-formed JWT proves nothing. Only a JWT whose signature you verified against a key you expected, using an algorithm you fixed rather than one the token named, carries any authority. Servers must pin the accepted algorithm and reject none outright.

Sources

  • “JSON Web Token (JWT),” RFC 7519, IETF. https://www.rfc-editor.org/rfc/rfc7519.txt . Supports the JWT as a compact, URL-safe representation of claims encoded as a JSON object and used as the payload of a JWS (signed) or JWE (encrypted) structure; its intent for space-constrained environments such as HTTP Authorization headers and URI query parameters; the Unsecured JWT with the alg header value “none” and an empty signature; and the security-considerations statement that a JWT’s contents cannot be relied upon in a trust decision unless cryptographically secured and bound to the necessary context.