Skip to content
Control Plane Labs

JWT, decoded: parts, algorithms, pitfalls

What the three parts of a JWT contain, how the signing algorithms differ, which claims you must validate, and the practices RFC 8725 tells you to adopt.

Ben Ennis

Published July 27, 2026

JWTs are the default bearer-token format for OAuth 2.0 and OpenID Connect, which means most engineers end up debugging one eventually. The format is simple. The validation rules are not, and the gap between “the signature verifies” and “this token authorizes this request” is where the interesting failures live.

The three parts

A JWT in compact serialization looks like this, and the dots are literal:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjJvMSJ9.eyJpc3MiOiJodHRwczovL2lkcC5leGFtcGxlIiwic3ViIjoiODQyIiwiYXVkIjoiYXBpOi9wYXltZW50cyIsImV4cCI6MTc4NTI5NjAwMCwiaWF0IjoxNzg1MjkyNDAwfQ.<signature>

Split on the dots and base64url-decode the first two segments and you get JSON:

{ "alg": "RS256", "typ": "JWT", "kid": "2o1" }
{
  "iss": "https://idp.example",
  "sub": "842",
  "aud": "api:/payments",
  "exp": 1785296000,
  "iat": 1785292400
}

The encoding is base64url — the URL-safe alphabet from RFC 4648 section 5, with - and _ replacing + and /, and padding omitted. That is why a JWT survives being put in a header or a query string, and why a standard base64 decoder sometimes rejects one.

The structure and the signing rules are RFC 7515 (JWS); the claim semantics are RFC 7519 (JWT). A token with five segments instead of three is not a JWS at all — it is a JWE, the encrypted variant, and it needs a decryption key rather than a verification key.

The point that bears repeating: a signed JWT provides integrity and authenticity, not confidentiality. Do not put anything in a payload that you would not put in a URL. Internal user IDs are usually fine; email addresses, role names that reveal internal structure, and anything resembling PII are a disclosure waiting to happen, because tokens end up in access logs, browser history, and error reports.

The claims that matter

RFC 7519 section 4.1 registers seven claims, and four of them are load-bearing for security:

  • iss — issuer. Must match the identity provider you expect, exactly.
  • sub — subject. The principal. Unique within the issuer, not globally.
  • aud — audience. The service the token is for. If your API accepts a token whose audience is a different service, you have built a token-forwarding vulnerability into your own architecture.
  • exp — expiry, as seconds since the Unix epoch. nbf (not before) and iat (issued at) are the companions; jti is a unique token identifier usable for replay detection.

Everything else is application-defined, but check the IANA JWT claims registry before you invent a claim name — there is a good chance the semantic you want is already registered, and colliding with a registered name that a library interprets is an unpleasant way to learn this.

OpenID Connect layers its own required validation on top for id_token, in section 3.1.3.7 of OpenID Connect Core, including the nonce check that binds the token to your authentication request. If you are consuming access tokens rather than ID tokens, RFC 9068 defines the JWT profile for OAuth 2.0 access tokens, and it is worth conforming to because it specifies typ: at+jwt and mandatory claims that make cross-service confusion detectable.

Algorithms, briefly

RFC 7518 (JWA) defines the algorithm identifiers. In practice you will see four families:

  • HS256/384/512 — HMAC with a shared secret. Symmetric: anyone who can verify can also sign. Fine inside one service, wrong between two parties.
  • RS256/384/512 — RSASSA-PKCS1-v1_5 with SHA-2. The most widely deployed.
  • PS256/384/512 — RSASSA-PSS. Preferred over RS* for new systems.
  • ES256/384/512 — ECDSA. Much smaller signatures than RSA at equivalent strength, which matters when tokens travel in headers.

EdDSA (Ed25519) is defined in RFC 8037 and is a good choice where library support exists. And alg: none exists in the specification for unsecured JWTs used inside an already-authenticated channel. It has no legitimate use in a token you receive over the network, and the correct posture is to reject it at the library level rather than to handle it.

Asymmetric algorithms need key distribution, which is what JWKS is for: the issuer publishes public keys as a JSON Web Key Set (RFC 7517), the kid header selects one, and consumers cache the set. Compute key identifiers with the RFC 7638 thumbprint method rather than inventing a naming scheme, so that a kid means the same thing to every party.

The validation checklist

RFC 8725 is the JWT best current practices document and the closest thing to an authoritative checklist. The items that catch real bugs:

Pin the algorithm (§3.1). Libraries MUST let the caller specify the acceptable algorithm set, and MUST NOT use anything else. Configure a one-element allowlist per token type. Never let the token’s own alg header select the verification method — that inversion of control is the root of the algorithm-confusion class of bug, where a token presented with a symmetric alg is verified using a public key that the attacker already has. The fix is structural: decide the algorithm from your configuration, then verify.

Validate every cryptographic operation and reject on any failure (§3.3). Partial validation is no validation.

Use keys with sufficient entropy (§3.5). Human-memorable passwords MUST NOT be used directly as HMAC keys. An HS256 secret should be 256 random bits from a CSPRNG, not a phrase in a config file.

Validate issuer and audience (§3.8, §3.9). The RFC is explicit that a relying party MUST reject a token whose audience is not associated with it.

Do not trust received claims (§3.10). Sanitize kid before using it in a lookup, and do not fetch keys from URLs supplied inside the token itself — jku and x5u are server-side request forgery vectors. Key locations belong in your configuration.

Use explicit typing (§3.11) and mutually exclusive validation rules (§3.12). If your system issues both ID tokens and access tokens, or password-reset tokens and session tokens, the validation rules must make it impossible to present one where the other is expected. typ is how you do that cheaply.

Add clock skew tolerance deliberately: a small leeway of 30 to 60 seconds on exp and nbf is normal, and anything larger extends the life of a stolen token.

The pitfalls that are architectural, not cryptographic

There is no revocation. A signed token is valid until it expires, full stop. Logout does not invalidate it. The mitigations are short expiry with refresh tokens, a denylist of jti values for the remaining lifetime, or opaque tokens plus introspection. Refresh tokens themselves can be revoked, per RFC 7009.

A JWT is a bad session cookie. Sessions need server-side invalidation and mutable state. If you find yourself putting a version counter in the payload so you can force re-issue, you have rebuilt a session store with extra steps and worse latency.

Size grows quietly. Roles and permissions in a payload can push a token past proxy header limits, at which point you get an 8 KB-shaped 431 or 502 that looks like a networking fault.

Browser storage is a real decision, not a detail. localStorage is readable by any script on the origin, so a single XSS becomes token theft; HttpOnly cookies are not, but need CSRF defenses. The OWASP testing guide for JWTs covers what a reviewer will look for.

To inspect a token safely — locally, with no upload — the JWT decoder on this site splits the segments, decodes the claims, and shows the expiry in your timezone. Decoding is not verification: it tells you what the token says, not whether the issuer said it.

Frequently asked questions

Is the payload of a JWT encrypted?+
No. It is base64url-encoded, which is reversible by anyone. If you need confidentiality, use JWE (RFC 7516), a five-segment token, or keep the sensitive data server-side and put only an identifier in the payload.
Can I revoke a JWT?+
Not the token itself. Options are short expiry plus refresh tokens, a server-side denylist keyed on jti until the token expires anyway, or opaque tokens with introspection. Refresh tokens can be revoked via RFC 7009.
Which signing algorithm should I choose?+
For a single service verifying its own tokens, HS256 with a 256-bit random secret. Across trust boundaries, use an asymmetric algorithm — ES256 for compactness, PS256 where RSA is required — and publish public keys as a JWKS. Pin the algorithm in configuration and never read it from the token.
Should I store tokens in localStorage or a cookie?+
A HttpOnly, Secure, SameSite cookie keeps the token out of reach of page scripts, at the cost of needing CSRF protection. localStorage is script-readable, so any XSS is a token compromise. For browser apps, prefer the cookie.
What is the minimum set of checks before trusting a JWT?+
Verify the signature with a key selected by your configuration, then check iss, aud, exp, and nbf with a small skew allowance, and confirm typ matches the token kind you expect. RFC 8725 is the full list.

Tags: #jwt, #oauth, #security, #authentication