tokenbench

JWT Decoder

Paste a token to read its header, payload and claims. Expired tokens decode too — that's usually why you're here.

Runs entirely in your browser.Open devtools → Network. Paste a token. Nothing is sent.Read the source · What we measure
Decoding happens on this page. Nothing is sent anywhere.

What a JWT actually is

A JSON Web Token is three base64url-encoded strings joined by dots:header.payload.signature. The header names the signing algorithm. The payload holds the claims. The signature is a MAC or digital signature over the first two segments.

The critical thing to understand — and the source of most JWT security incidents — is that the first two segments are encoded, not encrypted. Base64url is not a cipher. Anyone who holds the token can read every claim in it, without any key whatsoever. That is exactly what this page does.

So: never put a password, a private key, an internal database ID you'd rather not leak, or anything else sensitive into a JWT payload. The signature guarantees integrity (nobody altered the claims) and authenticity (someone with the key issued them). It guarantees nothing about confidentiality.

Decoding is not validating

These are separate operations, and conflating them is a real vulnerability class. To decode is to read. To validate is to check that the signature matches a key you trust, that the token has not expired, that iss and aud are what you expect, and that the algorithm is one you allow.

A decoder that refuses to display an expired or badly-signed token is doing you no favours — it is hiding the evidence you came for. This one always decodes what it can, and reports problems alongside the result rather than instead of it. When you actually need the signature checked, use the validator.

The registered claims, and what they mean

ClaimNameWhat it's for
issIssuerWho minted the token. Your server should check this against an expected value.
subSubjectWho the token is about — usually a user ID.
audAudienceWho is meant to accept it. A token for service A must not be accepted by service B.
expExpirationUnix seconds after which the token must be rejected.
nbfNot beforeUnix seconds before which it must be rejected.
iatIssued atWhen it was minted. Useful for age policies.
jtiJWT IDUnique ID, for replay detection and revocation lists.

All of them are optional in the spec, which is why this decoder flags a missing exp: a token with no expiry is valid until the signing key rotates, and a copy that leaks stays useful indefinitely.

Decoding a JWT in code

Every snippet below decodes without verifying. That is fine for inspecting a token you already trust, or for reading the kid before you look up a key — and it is a serious bug anywhere else. Never make an authorization decision on an unverified payload.

Node.js

// No dependency needed — it's just base64url and JSON.
const [header, payload] = token
  .split('.')
  .slice(0, 2)
  .map((seg) => JSON.parse(Buffer.from(seg, 'base64url').toString('utf8')));

console.log(header.alg, payload.exp);

Python

import base64, json

def decode_segment(seg: str) -> dict:
    # base64url in a JWT drops padding; add it back before decoding.
    padded = seg + "=" * (-len(seg) % 4)
    return json.loads(base64.urlsafe_b64decode(padded))

header, payload = (decode_segment(s) for s in token.split(".")[:2])
print(header["alg"], payload["exp"])

Go

import (
    "encoding/base64"
    "encoding/json"
    "strings"
)

parts := strings.Split(token, ".")
raw, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
    return err
}

var payload map[string]any
if err := json.Unmarshal(raw, &payload); err != nil {
    return err
}

Java

import java.util.Base64;
import java.nio.charset.StandardCharsets;

String[] parts = token.split("\\.");
String payload = new String(
    Base64.getUrlDecoder().decode(parts[1]),
    StandardCharsets.UTF_8
);
// Parse the payload with Jackson or Gson as needed.

Why the input is so forgiving

Tokens rarely arrive clean. They come with a Bearer prefix from an Authorization header, wrapped across lines by a terminal, percent-encoded from a query string, or quoted from a JSON log. This decoder strips all of that before parsing, and accepts standard base64 as well as base64url, with or without padding. If it still cannot read your token, it will tell you which of the three segments failed and why — never a blank screen.

Common questions

Is it safe to paste a real JWT here?

Yes, and you don't have to take our word for it. The decoder runs entirely in your browser — open devtools, switch to the Network tab, and paste a token. No request is made. The source is public, and the page works with your network disconnected.

Does decoding a JWT reveal the secret?

No. A JWT's header and payload are base64url-encoded, not encrypted — anyone holding the token can already read them. The signature proves the token was issued by someone holding the key, but it does not hide the contents. Never put anything in a JWT payload that the bearer should not see.

Why does my expired token still decode?

Because decoding and validating are different operations. Expiry is a claim inside the payload, not a property of the encoding, so an expired token decodes exactly like a fresh one. That is deliberate here — you are usually looking at a token precisely because it expired, and a tool that refuses to show you why is useless.

What does 'Invalid Signature' mean if the payload looks right?

It means the token was not signed by the key you supplied. Either you are checking against the wrong key, the token came from a different environment, or something modified it in transit. Use the validator to check the signature against a secret, PEM or JWKS.

Can this decode an encrypted token (JWE)?

No, and nothing can without the decryption key. A five-segment token is a JWE: its payload is ciphertext. This tool handles signed tokens (JWS), which is what almost everyone means by 'JWT'. If you paste a JWE, the decoder will tell you that is what you have.

The rest of the workbench