DevToolNow

JWT Anatomy: Header, Payload, and Signature Explained

DevToolNow Editorial Team···10 min read

A JSON Web Token is three Base64URL-encoded strings separated by dots. That description is technically complete and operationally useless. The interesting part is what each segment contains, what cryptographic guarantees it carries, and which of those guarantees evaporate the moment you misread the spec. This guide walks the wire format defined by RFC 7519 (JWT) and RFC 7515 (JWS), then covers the parts of the production picture the RFCs deliberately leave to you.

1. The wire format: three segments, two dots

A signed JWT has the shape header.payload.signature. Each segment is independently Base64URL-encoded (the URL-safe variant defined in RFC 4648 §5, with no padding). Decoding any segment is trivial — there is no encryption involved in a standard signed JWT, only signing. If the contents are sensitive, you need JWE (RFC 7516) or you need to keep the data on the server.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNzE0MzAwMDAwfQ
.H7HK_ufFb5Kt9NYIZ-9oQqqrh8K3oUNWvI6Zs-l4i2Q

That second-line break is purely visual; on the wire the token is one string with no whitespace. Splitting on the dot gives you the header, the payload, and the signature in that order.

2. The header: algorithm metadata

Decoded, the header is a small JSON object that tells the verifier how the signature was produced. The two fields you will see in essentially every JWT are alg (the signing algorithm) and typ (almost always the literal string "JWT"). When asymmetric signing is used, you will also see kid — a key ID that lets the verifier pick the right public key from a JWKS endpoint.

{ "alg": "RS256", "typ": "JWT", "kid": "2026-04-key" }

Common values for alg are HS256 (HMAC with SHA-256), RS256 (RSA-PKCS1-v1_5 with SHA-256), PS256 (RSA-PSS with SHA-256), and ES256 (ECDSA on P-256 with SHA-256). The full registry lives in IANA's "JSON Web Signature and Encryption Algorithms" table.

3. The payload: claims, registered and otherwise

The payload is a JSON object whose fields RFC 7519 calls claims. The spec reserves a small set of three-letter names — these are the "registered claims" you should recognise on sight:

  • iss — issuer. Identifies the principal that issued the token.
  • sub — subject. The principal the token is about, typically a user ID.
  • aud — audience. The service(s) the token is intended for.
  • exp — expiration time, as a Unix timestamp in seconds.
  • nbf — not before. Token must not be accepted before this time.
  • iat — issued at, also Unix seconds.
  • jti — a unique JWT ID, useful for revocation lists.

Everything else is a custom claim. OpenID Connect adds standard custom claims like email, name, and preferred_username; your application may add anything you like (role, tenant_id, permissions). Two things to remember: the payload is readable by anyone who has the token, and every claim you add is bytes the client has to send on every request. Treat the payload like a hot path, not a database row.

4. The signature: HMAC vs RSA vs ECDSA

The signature is computed over the literal bytes of base64url(header) + "." + base64url(payload). This is important — the verifier must check the signature against the original encoded segments, not against a re-encoded copy of the parsed JSON. RFC 7515 §5.2 spells this out explicitly.

HMAC (HS256/HS384/HS512) uses a single shared secret. It is fast and simple, but every party that can verify a token can also forge one. Use HMAC only when the issuer and the verifier are the same trust boundary — typically a single backend service.

RSA (RS256/PS256) and ECDSA (ES256) are asymmetric. The issuer holds the private key; verifiers only need the public key. This is the model identity providers use: the IdP signs, your APIs fetch its JWKS at /.well-known/jwks.json and verify. ECDSA produces dramatically smaller signatures (64 bytes for ES256 vs 256 bytes for RS256), which matters in cookie-based flows where total cookie size has hard limits.

5. The alg=none vulnerability and other classic failures

RFC 7519 permits an "Unsecured JWT" with alg: "none" and an empty signature. Some libraries, given a token with that header, used to skip signature verification entirely. The attack writes itself: change the payload, set the header to {"alg":"none"}, drop the signature, and a vulnerable verifier accepts the forged token. Auth0 disclosed the original wave of these CVEs in 2015 and the OWASP JWT cheat sheet still leads with it.

Two related bugs come up almost as often. The first is algorithm confusion: a verifier configured with an RSA public key, when handed a token with alg: "HS256", naively uses the public key as an HMAC secret. Since the public key is public, the attacker can sign anything they want. The fix is to declare an algorithm allow-list at the verifier and never trust the header's alg field as a routing decision. The second is missing or weak exp checks — silently accepting expired tokens, or having no expiration at all.

6. Refresh tokens and short-lived access tokens

A signed JWT has a single revocation strategy: wait for it to expire. There is no central ledger to invalidate. Production systems work around this by making access tokens short-lived (5–15 minutes is typical) and pairing them with a longer-lived refresh token. The refresh token is held by the client, exchanged at a token endpoint for a new access token, and — critically — can be revoked server-side because the server stores it.

Refresh token rotation (issuing a new refresh token on every refresh and invalidating the old one) is now the OAuth 2.0 Security BCP recommendation, especially for SPAs and mobile clients. If a refresh token leaks, the legitimate client will hit a reuse-detection error on its next refresh and the entire family of tokens can be revoked.

7. JWT vs session cookies: the honest comparison

JWTs make sense when you have multiple services that must verify identity without round-tripping to an auth service, when a third-party identity provider is involved, or when you specifically need OIDC. They are not automatically "more modern" than session cookies. A traditional opaque session cookie backed by a Redis or database session store gives you trivial revocation, smaller request headers, and a simpler mental model — at the cost of a backend lookup on each request, which is usually fine.

If you do use JWTs in a browser context, store them in HttpOnly Secure cookies rather than localStorage. The latter is readable by any JavaScript on your origin, which means any successful XSS exfiltrates every active token. This is one of the few points where the OWASP cheat sheet, Auth0's own documentation, and the spec authors all agree.

Need to inspect a JWT right now?

DevToolNow's JWT Decoder runs entirely in your browser — header, payload, and signature are decoded locally and never leave the page. Useful for debugging Auth0, Cognito, Firebase, and homegrown tokens.

Open JWT Decoder →

Frequently asked questions

Q. What is the difference between a JWT and a JWS?

A. JWS (JSON Web Signature, RFC 7515) is the lower-level construct: a signed payload encoded as three Base64URL segments. JWT (RFC 7519) is a specific use of JWS where the payload is a JSON object containing claims like iss, sub, and exp. Almost every JWT you encounter in the wild is technically a JWS with a JSON payload. There is also JWE (JSON Web Encryption, RFC 7516) which encrypts the payload, but it is rarer in practice.

Q. Should I use HS256 or RS256?

A. Use HS256 (HMAC-SHA-256) when the same service issues and verifies the token — it is faster and the symmetric secret never leaves your trust boundary. Use RS256 or ES256 when one service issues tokens and other services verify them, because verifiers only need the public key. Identity providers like Auth0, Okta, and Cognito all use asymmetric algorithms by default for exactly this reason.

Q. Is JWT secure if I just use HTTPS?

A. HTTPS protects the token in transit, but storage and validation are still your problem. Common failures include accepting alg=none, accepting any algorithm declared in the header without an allow-list, missing exp validation, leaking tokens in browser localStorage to XSS, and not rotating signing keys. The OWASP JWT cheat sheet enumerates these — review it before shipping.

Q. Why do I need a refresh token?

A. Access tokens should be short-lived (5–15 minutes is typical) so that a leaked token has limited blast radius. A refresh token is a separate, longer-lived credential that the client exchanges for a new access token. Refresh tokens should be stored more carefully (HttpOnly cookie or secure storage), be rotatable, and be revocable on the server. The pattern is documented in OAuth 2.0 (RFC 6749) and the OAuth 2.0 Security Best Current Practice draft.

Q. Should my new app use JWTs or session cookies?

A. If you control both the client and the server and run a single backend, opaque session cookies are simpler and easier to revoke. If you have multiple microservices that need to verify identity without calling an auth service, or you are integrating with a third-party identity provider, JWTs (or specifically OIDC ID tokens) are the right primitive. The honest answer is that most product teams over-reach for JWTs when a session cookie would do.

References

  • IETF RFC 7519 — JSON Web Token (JWT)
  • IETF RFC 7515 — JSON Web Signature (JWS)
  • IETF RFC 7518 — JSON Web Algorithms (JWA)
  • OWASP — JSON Web Token for Java Cheat Sheet and JWT Security Cheat Sheet
  • Auth0 — JSON Web Token Introduction and the original alg=none disclosure (2015)

Note: This guide is a general technical overview. Real-world JWT deployment depends on your identity provider, key management strategy, and threat model. Audit signing-key rotation and token storage with a security review before shipping to production.

About the DevToolNow Editorial Team

DevToolNow's editorial team is made up of working software developers who use these tools every day. Every guide is reviewed against primary sources — IETF RFCs, W3C/WHATWG specifications, MDN Web Docs, and project repositories on GitHub — before publication. We update articles when standards change so the guidance stays current.

Sources we cite: IETF RFCs · MDN Web Docs · WHATWG · ECMAScript spec · Official project READMEs on GitHub

Related tools on DevToolNow