Skip to content

JWT Decoder

Paste a JWT to read its header and payload. Timestamps are shown as real dates with an expiry verdict — "expired 3 hours ago" rather than the raw number 1516239022, which is the answer you decoded the token to find. Nothing is sent anywhere and nothing is stored.

Decoded in this page. Your token is not sent anywhere, not saved, and not put in the URL.

Before you paste a production token: a JWT is a live credential, like a password. Pasting one into any website is a risk you cannot verify from the outside — including this one. For a real token, decode it locally or use a short-lived test token instead.

Runs in your browser. Nothing uploaded.

How to use it

  1. Paste the token. A Bearer prefix or a whole Authorization: header line is fine — both are stripped for you.
  2. Read the status first. Expired, not yet valid, or still good, with the exact time.
  3. Check the claims. Header and payload are shown as formatted JSON, with the standard claims explained underneath and every timestamp turned into a real date.

No token to hand? Press “Load an example token” to see the output on the canonical example from the JWT specification.

What a JWT is made of

A JSON Web Token is three Base64url strings joined by dots:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0In0.SflKxwRJSMeKKF2QT4fwpM

Segment Contains Readable without a key?
Header The signing algorithm (alg) and token type (typ) Yes
Payload The claims — who the token is about, what it grants, when it expires Yes
Signature A hash of the first two segments, made with the signing key Yes, but it is meaningless without the key

The important word in that table is yes. Base64url is an encoding, not encryption. Anyone holding the token can read every claim in it, on any machine, with no key at all. The signature does not conceal the contents — it only proves the contents have not been altered since they were signed.

Which leads to the rule people most often break: never put anything confidential in a JWT payload. Not internal IDs you would not publish, not roles you would rather users did not see, not personal data. If it is in the token, it is readable by whoever holds the token.

Standard claim reference

Seven claims are registered in RFC 7519. Everything else is application-specific.

Claim Name What it means
iss Issuer Who created and signed the token
sub Subject Who the token is about — usually a user ID
aud Audience Who the token is intended for. Your API should reject tokens addressed elsewhere.
exp Expiration time Unix seconds. The token must be rejected at and after this moment.
nbf Not before Unix seconds. The token must be rejected before this moment.
iat Issued at Unix seconds. When the token was created.
jti JWT ID A unique identifier, used to stop a token being replayed

You will also meet OIDC claims like azp, nonce, scope and auth_time. The tool labels those too.

Note that all three time claims are seconds, not milliseconds. Passing a JavaScript Date.now() value straight into exp produces a token that expires in the year 56,000 — a real and surprisingly common bug.

How to tell if a JWT is expired

Look at exp. It is a Unix timestamp in seconds, and the token is invalid at and after that instant.

The problem is that exp looks like this:

"exp": 1516242622

Which answers nothing. You decoded the token to find out whether it had expired, and you have been handed an integer. Most decoders stop exactly there.

This one converts every time claim into a date in your local timezone and states the answer in words — “expired 3 hours ago”, or “expires in 42 minutes” — with a badge at the top of the results. That is the question you came with, so it is the first thing on the page.

It checks nbf as well. A token with a nbf in the future is not expired but is not usable yet either, and a service rejecting it looks identical to an expiry problem from the outside. Clock skew between two servers is a classic cause: the issuer’s clock runs 30 seconds fast, the receiver rejects everything for the first half-minute of each token’s life.

If there is no exp at all, the tool says so and flags it. A token with no expiry never becomes invalid on its own, so if it leaks it stays useful forever.

Why this tool does not verify signatures

It could. We decided not to, and the reasoning is worth stating plainly rather than leaving it as a missing feature.

Verifying an HMAC-signed token (HS256 and friends) requires the signing secret. A page that offers signature verification is a page asking you to type your production signing key into a website. Even done entirely in the browser with no network request — which is how it would be built here — it teaches a habit that leaks secrets, and you have no way to confirm the promise by looking at the page.

So there is no field for your secret, and there never will be. Verify signatures in your own code, with your own key, using your language’s JWT library. That is where verification belongs.

What this tool does is read the token, which needs nothing, and answer the questions reading it can answer.

Decoding Verifying
Needs a key No Yes
Tells you the claims Yes Yes
Proves the token is genuine No Yes
Proves it has not been tampered with No Yes
Safe to do in a web page Yes Not with a real key

The row that matters is the third. Decoding a token tells you what it claims. It does not tell you those claims are true. Never make an authorisation decision on a decoded-but-unverified token.

Security notes the tool raises

"alg": "none"

This means the token is unsigned. It is legal in the specification, for cases where integrity is guaranteed some other way. It is also a well-known attack: take a valid token, change the algorithm to none, rewrite the payload to make yourself an administrator, drop the signature, and send it. A library that trusts the header’s alg field when choosing how to verify will accept the forgery.

Well-maintained libraries have refused alg: none by default for years. If you see it on a token in your own system, that is a finding, not a curiosity.

Missing exp

A token that never expires cannot be timed out, and revoking it means maintaining a blocklist you probably have not built. Short-lived access tokens with a separate refresh token exist precisely to avoid this.

Very long lifetimes

The tool calls out tokens whose exp is more than about 90 days after iat. A year-long access token is a credential you cannot practically withdraw.

Algorithm confusion

Not something the tool can detect, but worth knowing: if a service verifies RS256 tokens with a public key, an attacker can sometimes re-sign a token as HS256 using that public key as the HMAC secret. A library that picks its verification method from the token’s own header will accept it. Always pin the expected algorithm on the server side rather than reading it from the token.

Is it safe to paste a JWT into a website?

Treat a JWT as a password, because functionally that is what it is. Anyone holding an unexpired token can act as its subject.

Any site you paste one into could send it to a server. This page does not: decoding happens in JavaScript in your browser, there is no network request, nothing is written to localStorage, and — unlike every other tool on this site — the token is deliberately never put in the URL. URLs end up in browser history, server logs and referrer headers, which is the last place a credential should be.

But you cannot verify that by reading a sentence promising it. So the honest advice, for a token from production:

  • Prefer a short-lived or test token when you just need to see the shape of the claims.
  • If you must decode a live one, use your language’s own library, or jq at a shell.
  • If you have already pasted a production token into some site, rotate it. That is cheap; being wrong is not.

What to do instead, with a real token

The tool carries a warning above the input box, and it’s worth restating here because it argues against itself: pasting a production token into any website is a risk you can’t verify, and that includes this one.

Everything on this page runs in your browser. Nothing is transmitted, nothing is stored, and unlike every other tool on this site there’s no shareable URL state — because putting a credential in a URL would put it in your history and in referrer headers.

All of which is true, and none of which you can check by reading it. A page that decoded locally and a page that quietly POSTed your token look identical from the outside. So the honest advice is the same one you’d get anywhere:

  • Use a test token. Most auth providers will issue a short-lived one. It decodes exactly the same way.
  • Decode locally if the token is real. It’s three lines — see the section below.
  • Treat a leaked token like a leaked password. If a production JWT has been somewhere it shouldn’t, revoke the session rather than hoping. A JWT is usually valid until it expires, and there is often no revocation list.
  • Check what’s actually in it before worrying. A token carrying nothing but a user ID and an expiry is a different problem from one carrying an email address and a role.

That last point cuts both ways. Plenty of JWTs contain personal data, because the payload is trivially readable and people forget that. Anyone who obtains the token can read every claim in it without a key — that’s what this page does.

Decoding a JWT without a website

Where How
Shell cut -d. -f2 <<< "$TOKEN" | base64 -d 2>/dev/null | jq
JavaScript JSON.parse(atob(t.split('.')[1].replace(/-/g,'+').replace(/_/g,'/')))
Node JSON.parse(Buffer.from(t.split('.')[1], 'base64url'))
Python jwt.decode(t, options={"verify_signature": False}) with PyJWT
Go jwt.ParseUnverified(t, jwt.MapClaims{})

Two traps in the shell version. Base64url uses - and _ where standard Base64 uses + and /, and JWT strips the trailing = padding, so base64 -d will often complain about invalid input even when it decodes correctly. The 2>/dev/null above hides that; a fully correct version restores the padding first.

Worked example

Paste the example token and you get:

  • Header: {"alg": "HS256", "typ": "JWT"} — signed with HMAC-SHA256.
  • Payload: sub of 1234567890, a name of John Doe, and iat of 1516239022.
  • That iat, read out: January 2018, in your own timezone — which is the point. The raw number tells you nothing; the date tells you the token was minted years ago.
  • Status: “No expiry set”, because the example has no exp claim, with the warning that a token like this never expires on its own.

Related work

The header and payload are Base64url, so the Base64 encoder and decoder will decode either segment by hand if you want to see the mechanics. The signature is an HMAC, which the hash generator covers. And if a token is arriving mangled in a query string, the URL encoder shows what percent-encoding did to it.

Frequently asked questions

How do I decode a JWT?

Paste the token above. A JWT is three Base64url segments separated by dots, and the first two are plain JSON once decoded — no key required. You can paste it with the "Bearer " prefix still attached and it will be stripped for you. The decoding runs in your browser, so the token never leaves your machine.

Is it safe to paste a JWT into an online decoder?

Treat a JWT like a password, because that is what it is. Any site you paste it into could send it to a server and use it until it expires. This page decodes entirely in your browser with no network request, and stores nothing — not in localStorage, not in the URL. But you cannot verify that claim by reading a sentence, so for a production token the safest answer is to decode it locally or use a short-lived test token.

How do I check if a JWT is expired?

Look at the exp claim, which is a Unix timestamp in seconds. The token is invalid at and after that moment. The tool above does this for you and states it in words — "expired 3 hours ago" or "expires in 42 minutes" — along with the actual date in your local timezone. It also checks nbf, which makes a token invalid before a given time.

Can a JWT be decoded without the secret key?

Yes, and that surprises people. The header and payload are Base64url encoded, not encrypted. Anyone holding the token can read every claim in it. The signature does not hide the contents — it only proves the contents have not been altered. Never put anything confidential in a JWT payload.

What is the difference between decoding and verifying a JWT?

Decoding reads the claims and needs nothing. Verifying checks the signature against a key and proves the token is genuine and unmodified. This tool only decodes. Verification belongs in your server code with your real key, not in a web page — which is also why we never ask for your signing secret.

What does "alg": "none" mean?

It means the token is unsigned and anyone can rewrite its payload. It exists in the spec for tokens whose integrity is guaranteed some other way, but it is also a well-known attack: change the algorithm to none, strip the signature, and a careless library accepts the forged token. If you see it on a real token, that is a finding. The tool flags it.

Guides that use this tool

Last updated: August 14, 2026