How JWT (JSON Web Token) Works — Structure, Signing, Verification Explained
A JWT is a compact, URL-safe token for securely passing claims between parties. It consists of a Base64URL-encoded header, payload, and cryptographic signature. This guide explains what each part contains, how signing works, and the difference between HS256, RS256, and ES256.
Last updated:
Core Concepts
- Three-part structure (header.payload.signature)
- Every JWT is three Base64URL-encoded segments separated by dots. The header declares the algorithm (alg) and key ID (kid). The payload carries claims — standardized ones like exp (expiry), iat (issued-at), iss (issuer), aud (audience), and custom ones like user_id or role. The signature prevents tampering: changing a single byte in the payload invalidates it.
- Symmetric vs asymmetric signing
- HS256 (HMAC + SHA-256) uses a shared secret — both the signer and the verifier know the same string. RS256 (RSA) and ES256 (ECDSA) use a private key to sign and a public key to verify. Asymmetric keys let you safely publish the public key (e.g. at /.well-known/jwks.json) without exposing the signing secret. Pro tip: never use HS256 across service boundaries; if anyone on either side leaks the secret, they can forge tokens for every user.
- Claims — exp, iat, nbf, aud, iss, sub
- exp (expires-at) is the most critical: a Unix timestamp after which the token should be rejected. iat (issued-at) helps detect token replay. nbf (not-before) defers validity to a future time. aud (audience) restricts the token to a specific API. iss (issuer) identifies who minted it. sub (subject) is the user or resource the token represents. Claim validation is separate from signature validation — a perfectly-signed token can still be invalid if the claims don't check out.
- The alg=none attack
- Some older JWT libraries accepted tokens whose header claimed alg: 'none' (unsigned). An attacker can strip the signature, change the payload, and the server would trust it. Modern libraries reject alg: none by default, but explicit algorithm allowlisting (algorithms: ['RS256']) is the definitive defense — it prevents algorithm-confusion attacks where an RS256 public key gets reused as an HMAC secret.
- JWKS (JSON Web Key Set)
- A public endpoint (/.well-known/jwks.json) that lists an issuer's public keys, each with a kid. Verifiers fetch this set, pick the key whose kid matches the token header, and verify the signature. Key rotation works by adding a new key alongside the old one in the set — old tokens validate against the old key until they expire.
Frequently Asked Questions
How do I verify a JWT without a library?
The short answer is: don't. Use a battle-tested library (jsonwebtoken in Node, PyJWT, golang-jwt, jose). If you absolutely must: take the first two segments, reconstruct headerB64 + '.' + payloadB64, run the signing algorithm against your key, base64url-encode the raw signature, and compare. Any timing (string ==) instead of crypto.timingSafeEqual opens a side channel.
Should I use HS256 or RS256/ES256 for my API?
RS256 (RSA) or ES256 (ECDSA) for anything with more than one service. HS256 forces you to share a secret between signer and all verifiers — a breach on one leaks the key for everyone. RS256/ES256 let you publish a public key and keep the private key in one place. ES256 has smaller keys and faster signing than RS256 at equivalent security.
Try these related tools
JWT Verifier →
Paste a JWT and its signing key (HMAC secret, RSA/EC public key in PEM or JWK) to verify the signature and inspect claims. Supports HS256/384/512, RS256/384/512, PS256/384/512, ES256/384/512. Also flags exp / nbf / iat time-based claims. 100% local — nothing is sent to any server.
JWT Generator →
Create signed JSON Web Tokens with HS256/384/512 (HMAC), RS256/384/512 (RSA), PS256/384/512 (RSA-PSS), or ES256/384/512 (ECDSA). Paste a raw secret, a PEM private key, or a JWK — signing runs entirely in your browser via the Web Crypto API.
JWT Decoder →
Decode JSON Web Tokens (JWT) to inspect the header, payload, and signature. Runs entirely in your browser — tokens are never sent to any server.
JWKS Generator →
Assemble multiple RSA/EC public keys into a standard JSON Web Key Set (JWKS, RFC 7517). Paste PEM (public or private — private components are stripped) or JWK, then auto-generate kid via SHA-256 thumbprint (RFC 7638). Ready to host at /.well-known/jwks.json — free, no signup, 100% local.
RSA Key Generator →
Generate RSA key pairs (2048, 3072, or 4096 bits) online for OAEP encryption, PSS signing, or RS256 JWTs. Exports PKCS#8 private key + SPKI public key as PEM, JWK, or DER (hex/base64). 100% local — the Web Crypto API runs in your browser, private keys never leave the tab.
ECDSA Key Generator →
Generate elliptic-curve key pairs (P-256, P-384, P-521) for ECDSA signatures or ECDH key agreement. Exports PKCS#8 / SPKI PEM, JWK, and DER (hex/base64). Perfect for ES256/ES384/ES512 JWTs. 100% local via the Web Crypto API — the private key never leaves your browser.