DevBench
All articles
authsecurityjwt

JWT Explained: Header, Payload, and Signature Decoded

May 4, 20266 min read

A JSON Web Token is three Base64URL-encoded strings joined by dots: header.payload.signature. The compact format makes JWTs easy to embed in HTTP headers, URL query strings, and cookies. But that compactness hides a lot of nuance — especially around security.

The header

The header is a JSON object describing the token type and the signing algorithm. Decoded, it typically looks like:

{
  "alg": "HS256",
  "typ": "JWT"
}

The alg field is critical. Common values:

  • HS256 — HMAC-SHA256. Symmetric: the same secret is used to sign and verify. Simple but the secret must be shared between services.
  • RS256 — RSA-SHA256. Asymmetric: sign with a private key, verify with a public key. Better for microservices — only the auth server needs the private key.
  • ES256 — ECDSA with P-256. Like RS256 but smaller key sizes and faster operations.
  • none — No signature. Never accept this in production. Attackers can forge tokens with "alg": "none" if your library doesn't explicitly reject it.

The payload

The payload contains claims — statements about the token subject. Standard registered claims:

  • sub — subject: the user ID or entity this token represents
  • iss — issuer: the service that issued the token (e.g. https://auth.example.com)
  • aud — audience: the intended recipient service(s)
  • exp — expiry: Unix timestamp after which the token is invalid
  • iat — issued at: when the token was created
  • nbf — not before: token is invalid before this time
  • jti — JWT ID: unique identifier for this token (enables revocation)

Important: The payload is Base64URL-encoded, not encrypted. Anyone with the token can read the payload — never put passwords, credit card numbers, or other secrets in a JWT payload.

The signature

The signature prevents tampering. For HS256, it is computed as:

HMAC-SHA256(
  base64url(header) + "." + base64url(payload),
  secret
)

If an attacker changes any bit of the header or payload, the signature won't verify. This is what makes JWTs tamper-evident — not confidential.

Common JWT pitfalls

  • Algorithm confusion attack — if your library accepts both HS256 and RS256, an attacker can take an RS256 public key (which is public), sign a token with it using HS256, and the library may accept it if it doesn't check the expected algorithm explicitly.
  • Not validating expiry — always check exp. A valid signature doesn't mean a token is still valid.
  • Not validating audience — a token issued for Service A should be rejected by Service B. Check aud.
  • Weak secrets for HS256 — a short or guessable HMAC secret can be brute-forced. Use at least 256 bits of random entropy.
  • Storing in localStorage — accessible to any JavaScript on the page. Prefer httpOnly cookies for tokens that authorize actions.

JWT vs sessions

JWTs are stateless — the server doesn't store anything. Sessions are stateful — the server stores session data and gives the client an opaque ID. JWTs scale horizontally without a shared session store, but can't be individually revoked (you have to wait for exp or implement a token blocklist — which adds state back anyway). For most applications, server-side sessions with a fast store (Redis) are simpler and more secure.

Try it yourself

Use the free browser-based JWT Debugger on DevBench — no signup, runs entirely in your browser.

Open JWT Debugger