JWT Decoder

Split a JWT into its header, payload and time claims, with pretty-printed JSON and readable dates. Display only, and it runs on your own device.

🌐 Español

🔒 Private by design: your text is processed locally in your browser and never uploaded to any server.

Three segments, and two of them are just JSON

A JSON Web Token looks like an opaque credential and is nothing of the sort. It is three chunks of Base64URL joined by dots: a header naming the signing algorithm, a payload of claims, and a signature over the first two. The first two carry no encryption whatsoever. Anyone holding the token can read them, which is exactly why a decoder is a five-second job rather than a cryptographic one.

Doing it by hand is not hard, it is just tedious in a way that adds up. Split on the dot. Remember that Base64URL swaps the plus and slash characters for minus and underscore, and usually drops the padding, so a naive decode throws. Then convert whatever integers you find in the payload from seconds-since-1970 into something a human can reason about. This page does those three steps and prints the result in one block.

Paste a token and read the report

  1. Paste the whole token into the box, all three dot-separated parts, exactly as it came out of an Authorization header, a cookie or a log line. Surrounding whitespace and a trailing newline are trimmed for you.
  2. Click JWT Decoder. The button carries the tool’s own name because this layout labels its action button with the page title.
  3. Read the output, then click Copy to clipboard to take it away, or Process another to clear the box for the next token.

Inside the notes block

The output has three labelled sections. Header and payload are printed as pretty-printed JSON with two-space indentation, exactly as the decoded bytes parsed. The notes section underneath is where the interpretation lives, and it only reports what is genuinely there.

Three time claims are recognised, exp, iat and nbf, and each is annotated only if it is present and holds a finite number. If none of them is, the notes say so in one line rather than inventing a date. A separate verdict line appears only when exp is a number, and it is compared against your own device clock at the moment you click. Decode the well-known example token that carries an issue time of 1516239022 and an expiry an hour later, and the notes open like this, with the raw signature segment printed on the line after:

=== NOTES ===
Expires (exp): 2018-01-18T02:30:22.000Z
Issued at (iat): 2018-01-18T01:30:22.000Z
This token is EXPIRED.

That verdict is a convenience, not a security check. A backend that accepts an expired token has a validation bug this page cannot see, and a token this page calls current can still be rejected by an issuer whose clock differs from yours.

Decoding is not verification, and the gap is the whole point

The signature is the only part that certifies anything, and checking it needs the shared secret for an HMAC algorithm such as HS256, or the issuer’s public key for RS256 and ES256. A generic page in your browser has neither and should not be inviting you to paste a production signing secret into it. So the third segment is reproduced as-is, flagged as not decoded and not verified.

If you do already hold the secret for an HMAC-signed token, you can do the arithmetic yourself: take the first two segments including the dot between them as the message, run them through the HMAC Generator with the same secret, and compare. Its Base64 output uses the standard alphabet, so swap plus for minus, slash for underscore and drop the trailing equals signs before the two strings will match character for character.

Multi-byte claims, and the decoder underneath

Segment decoding reuses the same routine that powers Base64 Decode, which is why awkward input survives it. Whitespace is stripped, the URL-safe alphabet is normalised back to standard Base64, missing padding is restored based on the length remainder, and the resulting bytes go through a strict UTF-8 decoder rather than being read one byte per character. A display name with an accent or a non-Latin script comes back intact instead of as mojibake, and bytes that are not valid UTF-8 are reported rather than silently mangled.

The four questions a decoded token usually settles

Most visits are one of a handful of questions. Which scopes did the identity provider really put in this access token. Why does the API insist the session is over, when the app thinks it just logged in. Does the header declare the algorithm the backend expects, or did something downgrade it. Did the custom claim the mobile team swears they are sending actually arrive.

All of those are read-only questions, which is why this page is deliberately read-only. When you need to go the other way and mint a token, the JWT Generator takes a header, a payload and a secret and signs a real one; it reads the same === HEADER === and === PAYLOAD === marker lines this page emits, so those two sections carry across once you drop the notes block from the bottom and add a secret section of your own. Pretty-printing an awkward claim value on its own is a job for the JSON Formatter & Validator, and if a token arrived inside a shell command, the Curl Command Converter will untangle the request around it.

See it in action

Screenshot of the JWT Decoder tool with the sample input “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ…”
JWT Decoder mid-process: the sample input “eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ…”.
Screenshot of the JWT Decoder result screen showing the generated output “=== HEADER === { "alg": "HS256", "typ": "JWT" } …”
The finished result: the generated output “=== HEADER === { "alg": "HS256", "typ": "JWT" } …”. The download link is a local blob URL — the file never leaves your device.

Frequently asked questions

Does a token decoding cleanly here mean it is genuine?

It does not, and treating it that way is the classic mistake. A JWT's first two segments are Base64URL-encoded JSON, not encryption and not a seal, so anyone can decode a token, rewrite the role or the subject inside it and re-encode something that looks just as convincing. Only checking the third segment against the issuer's secret or public key proves anything, and this page never attempts that. Read the output as what the token says about itself.

Why are the dates shown in UTC rather than my own time zone?

The time claims are converted with the JavaScript engine's ISO 8601 formatter, which always renders in UTC and ends the string with a Z. That keeps the output identical for everyone reading the same token, which matters when you are pasting it into a bug report. If you would rather see the same instant against your local clock, feed the raw number to the Unix Timestamp Converter, which prints a UTC block and a local block side by side.

My token came back with a vague failure message. Where did the real reason go?

Into the browser console. The decoding logic raises precise errors, naming the segment count it actually found or which of the two segments failed to produce JSON, but the shared paste-in layout replaces every raised message with one fixed sentence about checking your input. Open the developer console to read the original. In practice a wrong segment count means a copy that clipped a dot, and a decode failure usually means truncated Base64.

The notes say no standard time claims were found, but I can see an exp in the payload above.

Two things produce that combination. The scan only accepts a finite number, so an exp stored as a quoted string is skipped rather than guessed at. The other case is a payload whose top level is an array or a bare value rather than a JSON object, which is unusual but legal enough to survive decoding; the payload still prints in full, and the claim scan simply has no object to look at.

Why is the third segment printed raw instead of decoded like the others?

Because it is not text and not JSON. That segment holds the raw bytes of an HMAC or a signature, Base64URL-encoded, so pushing it through a JSON parser would only ever fail. It is printed verbatim so you can compare it against a value you computed elsewhere, and the note beside it states plainly that it was neither decoded nor verified.

Can I decode a second token without reloading the page?

Yes. Once a result appears, the paste box and the action button are swapped out for the output and two buttons, so click the one that offers to process another and the input comes back empty and ready. Nothing from the previous token is kept anywhere, and a refresh has the same effect.

Related tools