Loading 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.
Paste a token above and it is taken apart here.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.