JWT Decoder
Paste a JWT. See the decoded header and payload, with claim explanations. Switch to the Verify tab to check the signature with a secret. Switch to Sign to generate test JWTs.
What is a JWT?
A JSON Web Token is three base64url-encoded segments joined by dots:
header.payload.signature. The header says how the token
was signed. The payload contains claims (user identity, expiry, etc.).
The signature lets the recipient verify both haven't been tampered
with — but only if they have the right secret or public key.
The first two parts aren't secret. Anyone can base64-decode them. JWTs are tamper-evident, not encrypted. See What is a JWT? for the full story.
How this tool compares to jwt.io
jwt.io is the canonical JWT inspection tool — well-built, free, also client-side. This tool's differences:
- Smaller bundle. Under 10KB of JS for the decoder. jwt.io is heavier.
- Standard-claim explanations inline. Each
iss,sub,exp, etc. is annotated with what it means and (for time fields) decoded to a human-readable date. - Privacy framing front and center. The "100% client-side" badge is on every page so security-conscious users can verify without doubt.
- Tabbed UX — decode / verify / sign in one widget, keyboard-friendly.
Common use cases
- Debugging auth bugs. Paste the JWT from a failing request, check the
expclaim — token expired? Wrong issuer? Missing scope? - Inspecting OAuth / OIDC tokens. Most OAuth identity tokens are JWTs. Paste them here to see what claims an IdP is returning.
- Generating test fixtures. Use the Sign tab to create JWTs with specific claims for unit tests.
- Verifying integration partners. Cross-check signatures during onboarding to confirm both sides are using the same secret.
The three parts in detail
Header
{
"alg": "HS256", // signing algorithm
"typ": "JWT", // type — almost always "JWT"
"kid": "key-2026" // optional: hint for key rotation
} Payload (claims)
{
"iss": "https://auth.example.com", // issuer
"sub": "user-12345", // subject
"aud": "api.example.com", // audience
"exp": 1735689600, // expiry (Unix seconds)
"iat": 1735603200, // issued at
"scope": "read:users write:users", // custom claim
"email": "alice@example.com" // custom claim
} Signature
For HS256:
HMAC-SHA256(base64url(header) + "." + base64url(payload), secret).
For RS256: similar but using RSA-SHA256 with a private key. The
signature is then base64url-encoded as the third segment.
Standard claims (JWT-spec)
See the claims reference for every registered claim, what it means, and when to use it.
Try the related tools
- Verify HMAC signature — paste a JWT + secret, get a yes/no
- Sign HS256 — generate test JWTs from header/payload/secret
- What is a JWT? — the full explainer
- JWT vs session cookies — when each is the right call
FAQ
Is my JWT sent anywhere?
No. Decoding is just base64url + JSON.parse, both built into your browser. Verification and signing use the Web Crypto API — also browser-native. Open DevTools → Network and verify: no requests are made.
What does it mean that the signature isn't verified by default?
Decoding a JWT just means base64-decoding the three segments — anyone can do it without the secret. Decoded ≠ trusted. Until you verify the signature with the right secret/key, the contents could have been tampered with. Use the Verify tab to check.
Why HMAC only? What about RS256 / ES256?
HMAC (HS256/384/512) uses a single shared secret — trivial to verify in a browser. RSA and ECDSA need a public key in PEM or JWK format and a few more lines of crypto setup. We're considering adding RS256 support; vote with your email signup.
Should I sign production JWTs with this tool?
No. The Sign tab is for testing — generating fixtures, exploring claim shapes, debugging server bugs. Don't paste a real production secret into a web form. For real signing, use a library on a server you trust.
What is the 'alg: none' attack?
Some old JWT libraries accepted tokens with alg: none as 'no signature required.' An attacker could re-sign any token that way and pass verification. Modern libraries reject none by default. This tool's verifier requires HS256/384/512 explicitly — none won't be accepted.
What happens if I change the payload?
The signature won't match the new payload, so any properly-implemented verifier will reject the modified token. The decoder will still parse the modified token (because base64-decode doesn't validate signatures), but verification will fail. That's the whole point of signing — tamper-evidence.