UUID Studio

JWT Decoder

Decode JSON Web Tokens without sending them to a server.

  • 🔒 No data stored or uploaded
  • âš¡ 100% client-side
  • 🆓 Free, no account

Need more than one tool at a time? Open the full Workbench - or press Ctrl+K to jump to any tool.

Hash, HMAC, AES-GCM/CBC + RSA-OAEP, codecs, JWT decoding, UUID v4, and secure random - all client-side. JWTs use Base64URL (three segments), not a single MIME Base64 block - use Decode JWT below, not raw Base64 decode.

About JWT Decoder

A JWT is three Base64URL-encoded segments joined by dots: a header saying which algorithm signed it, a payload of claims, and a signature over the first two. Decoding it is just Base64URL - no key required, no cryptography involved - which is the single most important thing to understand about the format.

That means a JWT is not encrypted. Anything in the payload is readable by anyone holding the token, including the user, their browser extensions, and whatever logged the Authorization header. Do not put anything in a JWT you would not print on a postcard.

It also means decoding tells you nothing about whether a token is genuine. A decoded token that looks perfect may have been forged wholesale, or had its payload rewritten. Only signature verification, against a key you trust, establishes authenticity - and that is a different operation, on a different page.

The registered claims are worth knowing by name: iss (issuer), sub (subject - usually the user id), aud (audience - which API the token is for), exp (expiry), nbf (not before), iat (issued at) and jti (a unique token id). exp, nbf and iat are NumericDate values - seconds since the Unix epoch, not milliseconds - and mixing up the unit is one of the most common causes of a token that is somehow always expired or never valid.

Base64URL is not standard Base64: it uses - and _ in place of + and /, and drops the = padding. That is why pasting a JWT segment into a plain Base64 decoder often fails or produces mangled bytes. Everything here is decoded in your browser, so a real production token is not transmitted anywhere.

How to use the JWT Decoder

  1. Paste the whole token, including both dots. Strip a leading 'Bearer ' if you copied it from an Authorization header.
  2. Read the header to see the algorithm (alg) and, if present, the key id (kid) that tells you which key should verify it.
  3. Read the payload claims. Check exp against the current time - remember it is in seconds, not milliseconds.
  4. If you need to know whether the token is actually valid rather than merely well-formed, use the JWT verifier with the signing key.

Examples

  • Sample HS256 JWT
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT Decoder in code

The same operation this tool performs, in the languages you are most likely to need it.

JavaScript - decode without verifying
function decodeJwt(token) {
  const [h, p] = token.split(".");
  const json = (seg) =>
    JSON.parse(
      // Base64URL -> Base64, then restore the padding
      atob(seg.replace(/-/g, "+").replace(/_/g, "/").padEnd(
        seg.length + ((4 - (seg.length % 4)) % 4), "="
      ))
    );
  return { header: json(h), payload: json(p) };
}

// For payloads containing non-ASCII, atob alone mangles UTF-8:
const utf8 = new TextDecoder().decode(
  Uint8Array.from(atob(b64), (c) => c.charCodeAt(0))
);

// This is DECODING, not verification. Never trust these claims
// for an authorisation decision.
Node.js
import jwt from "jsonwebtoken";

// Decode only - no key, no validation. Debugging use only.
const decoded = jwt.decode(token, { complete: true });
console.log(decoded.header, decoded.payload);

// What you must do before trusting anything in it:
const verified = jwt.verify(token, publicKey, {
  algorithms: ["RS256"],          // pin it - never accept the token's own alg
  issuer: "https://auth.example.com",
  audience: "https://api.example.com",
});
Python (PyJWT)
import jwt

# Decode without verifying - for inspection only
claims = jwt.decode(token, options={"verify_signature": False})
print(claims)

# The real thing
claims = jwt.decode(
    token,
    key=public_key,
    algorithms=["RS256"],        # explicit list, always
    issuer="https://auth.example.com",
    audience="https://api.example.com",
)
Command line
# Decode the payload of a JWT held in $TOKEN
echo "$TOKEN" | cut -d. -f2 \
  | tr '_-' '/+' \
  | base64 -d 2>/dev/null \
  | python3 -m json.tool

# tr converts Base64URL to standard Base64; base64 -d may warn about
# missing padding, which is why stderr is discarded.

# Check the expiry as a readable date
echo "$TOKEN" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null \
  | python3 -c "import json,sys,datetime as d; print(d.datetime.fromtimestamp(json.load(sys.stdin)['exp']))"

When you need this

  • Checking what claims a token from your identity provider actually contains.
  • Reading exp to work out whether a 401 is an expiry problem or something else.
  • Finding the kid so you know which key in a JWKS should verify the token.
  • Confirming which algorithm an issuer is using before you configure verification.
  • Inspecting a token from a bug report to see which tenant, scope or role it carries.

Common problems and what causes them

"Invalid token" / "jwt malformed"
The string is not three dot-separated segments. Usual causes: a 'Bearer ' prefix left on the front, the token truncated by a log line or a column width, a URL-encoded token where dots or underscores were escaped, or whitespace and newlines from copying out of a terminal.
Decoding fails on a token with non-ASCII claims
atob returns a byte string, not UTF-8 text, so accented characters and emoji come out mangled. Convert through Uint8Array and TextDecoder, as in the JavaScript snippet above.
Standard Base64 decoders rejecting a segment
JWT segments are Base64URL: - and _ instead of + and /, with padding stripped. Translate those characters and re-add = padding to a multiple of four before using a standard decoder.
exp compared against milliseconds
exp, iat and nbf are seconds since the epoch. Comparing against Date.now() (milliseconds) makes every token look expired by a factor of a thousand. Use Math.floor(Date.now() / 1000).
Treating decoded claims as verified
This is the security mistake that matters. Anyone can craft a token with any claims; decoding does not check the signature. If a decision depends on sub, roles or scope, verify the signature with a pinned algorithm and a trusted key first.
Assuming the payload is confidential
A signed JWT is encoded, not encrypted - the payload is plainly readable. Personal data, internal ids and anything sensitive should not be in it. JWE exists if you genuinely need encrypted claims.

FAQ

Does decoding a JWT verify it?
No, and the distinction is critical. Decoding is Base64URL and needs no key; verification is a cryptographic check of the signature against a key you trust. A token can decode perfectly and be entirely forged, so never make an authorisation decision on decoded claims alone.
Is a JWT encrypted?
Not by default. The standard signed JWT (JWS) is Base64URL-encoded and fully readable by anyone who has it. If you need the contents hidden you want JWE, which is a different and considerably less commonly used format.
Why does my token fail to decode?
In order of frequency: a 'Bearer ' prefix, truncation, URL-encoding, stray whitespace or newlines, or a standard Base64 decoder choking on Base64URL characters and missing padding.
How do I read the expiry time?
The exp claim is seconds since the Unix epoch. In JavaScript, new Date(payload.exp * 1000); in Python, datetime.fromtimestamp(payload['exp']). The times shown on this page are already converted.
Can I edit a JWT's payload?
You can change the Base64URL text, but the signature will no longer match and any correct verifier will reject it. To produce a valid token with different claims you need the signing key - which is exactly the property JWTs exist to provide.
Is my token sent to a server when I paste it here?
No. Decoding is pure string manipulation in your browser, and there is no request. You can confirm it in your network tab, or load the page and then go offline.
Is it safe to paste production JWTs?
Processing is local, but anyone with screen access can read them. Prefer dev tokens; rotate secrets if exposed.
Why not use plain Base64 decode?
JWT uses Base64URL without padding. Use this decoder, not the MIME Base64 tool.

Related reading