Free Handy Tools

JWT Decoder

This decodes. It does not verify. Reading a token tells you what it claims, not whether those claims are true. Checking the signature needs the key it was signed with — an HMAC secret or the issuer’s public key — which only the issuing service and its verifier hold. A token that decodes cleanly here may still be forged, tampered with, or expired: treat everything below as an unverified assertion.

Reading

Paste a token above and it is taken apart here.

The seven registered claims

  • iss Issuer — Who minted and signed the token.
  • sub Subject — Who or what the token is about — usually a user id.
  • aud Audience — Who the token is meant for. A recipient not named here must reject it.
  • exp Expiration time — The instant after which the token must be refused.
  • nbf Not before — The instant before which the token must be refused.
  • iat Issued at — When the token was created.
  • jti JWT ID — A unique id for this token, so a replay can be spotted.

The token never leaves this page. It is split and base64url-decoded by JavaScript in your own browser, with no request made anywhere — which matters, because pasting a live access token into a website is handing that site your session.

Reading a token without trusting it

A JSON Web Token is three pieces of base64url text joined by dots: a header saying how it was signed, a payload of claims, and a signature over the first two. Paste one here and both readable segments are decoded and pretty-printed, every claim is listed, the registered claims are named, and the time claims become dates.

What comes back is a faithful report of what a token says about itself — a different thing from evidence that any of it is true.

Why no green tick appears here

A signature is checked with a key, and this page has none. For the HMAC family — HS256 and its relatives — the key is a shared secret held by the issuer and by whatever accepts the token, and publishing it into a web page would defeat the point of having it. For RS256, ES256 and the rest, the check needs the issuer’s public key, usually fetched from a JWKS endpoint.

So decoding proves nothing. A tampered payload decodes exactly as cleanly as a genuine one: editing the middle segment does not stop it being valid base64url, it only stops the signature matching, and nothing here tests that. Never make an access decision from a decoded token.

One header value makes this concrete. Setting alg to "none" is legal and means the token carries no signature at all. The classic attack was to strip a real signature, set that value, and hand the result to a verifier that took the header’s word for which algorithm to use — which is why any library worth using demands an allow-list from the caller, and why this tool flags the value loudly.

A worked token, claim by claim

Take a header of {"alg":"HS256","typ":"JWT"} and a payload carrying iss, sub, aud, a jti, iat 1767225600 and exp 1767229200. The tool names sub as the Subject and aud as the Audience, and reads those numbers as 1 January 2026 at 00:00 UTC and one hour later — a difference of 3,600 seconds, which is a normal lifetime for an access token.

Those numbers are NumericDate values: seconds since the Unix epoch, not milliseconds. Passing Date.now() straight into a claim lands an expiry in the year 56000; reading seconds as milliseconds dates a 2026 token to 1970. An exp the tool cannot read as a number is shown raw rather than invented into a date.

Claims outside the registered seven are private claims — scope, email, tenant_id, roles — meaning whatever the issuer decided, so the tool labels them as the issuer’s own.

Base64url, and what the payload is not

The segments are base64url, not base64: plus becomes hyphen, slash becomes underscore, and the trailing equals signs are dropped so a token survives being pasted into an address. That is why a decoder insisting on padding rejects perfectly good tokens.

It also means the payload is encoded, not encrypted. Anyone holding the token reads every claim in it, so a JWT is the wrong place for anything you would not put on a postcard. Contents that must stay hidden need JWE, which has five segments rather than three; this tool recognises one and says so.

Decoding happens in JavaScript on this page with no request made anywhere, which matters because a live token is a session. Even so, treat any token pasted into any tool as having had a walk outside.

Encrypted, expired, or simply edited

Is the payload encrypted?

No. It is base64url text, an encoding anyone reverses in a second — this page does it with no key at all. Signing protects a token from being changed, never from being read.

Why does the expiry show as 1970?

Because a NumericDate is seconds since the epoch and something on the way in or out treated it as milliseconds. Date.now() has to be divided by a thousand before it goes into a claim.

Can I change a claim and keep using the token?

Not against anything that verifies properly. Editing the payload invalidates the signature and a correct verifier rejects it. A service that does accept an edited token has a serious bug, rather than you having found a useful trick.

How should a token actually be verified?

On the server, with a maintained library, given an explicit list of acceptable algorithms and the issuer’s key or JWKS URL. Then check iss, aud, exp and nbf against what you expect: a signature proves who wrote the token, not that it was meant for you.

Is it safe to paste a production token here?

Nothing is transmitted from this page, so no copy is kept here. The wider habit is worth resisting: plenty of online decoders do post the token, and a leaked one is a logged-in session for whoever holds it.

What is the difference between exp and nbf?

exp is when the token stops being valid and nbf is when it starts, so a token issued now for use at midnight carries both. Verifiers normally allow a few seconds of leeway on each to absorb clock skew between machines.

How long should an access token live?

Short enough that a stolen one expires before it is useful — minutes rather than days — with a refresh token doing the long-lived work behind a revocation check. A signed token cannot be withdrawn once issued, which is the whole argument for keeping the lifetime brief.