sitekits.dev
press ⌘K to switch tools
SECURITY

JWT Token Decoder

Decode and inspect JSON Web Tokens.

local
jwt-decoder

Decoding only — the signature is not verified. Nothing is sent anywhere.

§01 ABOUT THIS TOOL

Overview

A JWT is three base64url-encoded parts separated by dots: a header saying how it was signed, a payload of claims, and a signature over the first two. The encoding is not encryption. Anyone holding the token can read every claim in it, which is worth internalising before you put anything in a payload.

This tool decodes the first two parts and formats them, then pulls out the three time claims and converts them from Unix seconds into timestamps you can read. It does not verify the signature — see below for why that is the right call rather than a missing feature.

How to use

  1. Paste the token. Everything after Bearer — the scheme itself is not part of the JWT.
  2. Read the header to see the algorithm and, if present, the key id.
  3. Read the payload for the claims.
  4. Check the timestamp table for iat, nbf, exp and the expiry verdict.

The registered claims

ClaimNameNotes
issIssuerWho minted the token. Your server should check this against an allowlist
subSubjectWho the token is about. Usually a stable user id, not an email
audAudienceWho the token is for. A token minted for another audience must be rejected
expExpirationUnix seconds. After this, reject
nbfNot beforeUnix seconds. Before this, reject
iatIssued atUnix seconds. Useful for “re-authenticate if older than N”
jtiJWT IDUnique id, so a token can be revoked or replay-checked

All seven are optional in the spec. A token that omits exp never expires on its own, which is a design decision worth noticing when you see it.

Why signature verification is not offered

Verifying a signature needs the key: the shared secret for HS256, or the public key for RS256 and ES256. For the symmetric case, the key that verifies is also the key that signs — so a page that offered verification would be asking you to paste the credential that lets anyone mint tokens for your system. There is no version of that which is safe to build.

Decoding is still worth doing. Most JWT debugging is “what does this token actually say” — wrong audience, missing scope, an exp five minutes in the past, a sub that is an email when your code expects a UUID. All of that is visible without the key.

What decoding cannot tell you is whether the token is genuine. A JWT you received is a claim, not a fact, until your server checks the signature against a key it trusts.

The alg field is not a decision your verifier should delegate

The header’s alg describes what the sender used. A verifier that reads alg and picks its algorithm accordingly is trusting attacker-controlled input, which produces two classic breaks:

  • alg: none. The unsecured JWT is legal in the spec. A verifier that honours it accepts any payload with an empty signature.
  • RS256 swapped for HS256. If a library takes the public key and uses it as an HMAC secret, an attacker who knows your public key — it is public — can sign tokens you will accept.

Both are configuration problems, not cryptography problems. Pin the expected algorithm in your verifier and reject anything else, rather than reading it from the token.

Examples

  • A 401 you cannot explain. Decode and check aud. A token minted for one API and presented to another is the most common cause, and the error message rarely says so.
  • An intermittent 401. Check exp against iat. A short lifetime plus a client that caches the token is intermittent by construction.
  • A permission that should work. Look for scope or roles in the payload. If the claim is absent, the problem is at the issuer, not in your authorization code.
  • Auditing what you put in a token. Decode one of your own. Anything in there — email address, internal ids, feature flags — is readable by whoever holds the token, including the browser it is stored in.

Where a token should live in a browser

This comes up every time someone decodes a token and notices it is readable. The short version: because a JWT is a bearer credential, anything that can read it can use it.

localStorage is readable by any script on the origin, which means an XSS takes the token with it. It is also never sent automatically, so you cannot be CSRF’d through it — you trade one class of bug for another.

An HttpOnly cookie cannot be read by script at all, so XSS cannot steal it. It is sent automatically, so it needs SameSite=Lax or Strict plus Secure, and any state-changing endpoint needs its own CSRF defence.

The usual conclusion for a browser app is a short-lived access token kept in memory only, refreshed from an HttpOnly, Secure, SameSite refresh-token cookie. That way an XSS gets a token with minutes of life rather than a credential that lasts weeks, and the long-lived secret is never reachable from JavaScript.

Whichever you pick, exp is doing the real work. A token with a one-hour lifetime and no revocation list is valid for one hour after it is stolen, and nothing on the client side changes that.

Notes

Padding is added before decoding, because base64url normally omits it. Tokens copied out of logs sometimes arrive with the padding intact, and both forms work here.

The expiry verdict uses your local clock, so it is a convenience rather than an authority. If you need to compare against a specific moment, use the Unix timestamp converter on the raw exp value.

Payloads are not always JSON in principle — the spec allows any content type — but every JWT in practice carries a JSON object, and a payload that fails to parse as JSON is displayed as raw text rather than hidden.

FAQ
Does this verify the signature?
No, and that is deliberate. Verifying requires the signing key, and pasting a signing key into a web page is exactly the mistake this tool should not encourage. Decoding tells you what a token claims; only your server can tell you whether the claim is true.
Is my token sent anywhere?
No. The token is split on dots and base64url-decoded in the page. There is no network request. Still treat a token you paste anywhere as spent — a bearer token in your clipboard history or browser session is a credential you no longer fully control.
Why does it say EXPIRED when my API still accepts the token?
The status compares exp against your computer's clock. If your clock is off, or the issuer allows clock skew (a few minutes is common), the two verdicts can disagree. The token's own exp value is the authoritative number; the label is a convenience.
Can I decode a token with only two parts?
Yes. Two parts is a valid unsecured JWT (alg: none) or a token you pasted without its signature. The tool needs the header and payload; the signature is not used.
The payload looks like nonsense. What went wrong?
Most often the token is encrypted rather than signed (a JWE, which has five parts) or something re-encoded it — copying from a terminal that wrapped the line, or from a JSON string that escaped it. A JWS payload is plain base64url and always decodes to JSON.