JWT Decoder
Decode a JSON Web Token to read its header and payload, with human-readable expiry - all in your browser.
- Free, no account
- No watermark
- No usage limit
About the JWT Decoder
A JWT is a live credential, so before you paste one anywhere, it's worth asking where it goes. That header.payload.signature blob you copied out of an Authorization header is often the working key to someone's session, and plenty of "online JWT decoder" sites do the decoding on their own server, which means your token takes a trip to a stranger's machine and might sit in their logs long after you've moved on. This one does all its work locally. It splits the token, base64url-decodes the header and payload, and pretty-prints the claims right here in your browser. Nothing is uploaded, nothing is stored, and closing the tab takes the token with it.
It also answers the question you probably showed up with. Point a basic decoder at a token and it hands back raw JSON with a line like "exp": 1731542400, a ten-digit number that means nothing until you run it through a converter. This one turns every time claim (iat, exp, and nbf) into a readable date in your own timezone, then checks exp and nbf against your device clock and tells you straight out whether the token is expired, not yet valid, or good right now. When a request just came back 401, that single line is usually the whole answer.
How to use
- Paste the token into the box. It's one string with three parts split by dots, like
eyJhbGci....eyJzdWIi....signature. If you grabbed it from anAuthorizationheader, delete theBearerin front first. - Read the header. A small JSON object naming the signing algorithm (
alg), usually the type (typ), and a key id (kid) when the issuer set one. - Read the payload. This is the part you're after: the claims. Standard ones like
sub,iss, andaud, alongside whatever custom fields the issuer added,email,role,tenant_id, that kind of thing. - Check the timestamps panel. Any
iat,exp, ornbfpresent shows up as both the raw epoch seconds and a human date, so you're not doing mental math on Unix time. - Read the validity line. It compares
expandnbfto your clock and flags the token as expired, not yet active, or currently valid. - Copy the header or payload with one click to drop into a bug report, or hit Clear and start over.
Paste something that isn't a well-formed JWT, a missing segment or a chunk that isn't valid base64url, and you get a plain inline error telling you what's wrong instead of a blank box.
Why your token never leaves this tab
Here the privacy question isn't abstract, because the string you're pasting can, right that second, authenticate as a real user against a real API. Hand it to the wrong site and someone else could walk away with a working login to a live account. That's the whole reason this runs in your browser and always will. The decode uses the same atob your browser already ships with, no request leaves the page, and there's no server on our end that could log a token even if we wanted it to.
You can check that yourself. Open your browser's network tab, paste a token, and watch nothing fire. The claims appear and not one byte went out. A server-side decoder asks you to simply trust that it threw the token away afterward, and for a live credential that is a lot of trust to hand a tool you found in a search result. Treat any production token like a password. Assume anything pasted into a website might be kept, and this tool makes that assumption free, because there's nowhere for the token to travel.
Reading a token when something's broken
Most of the time you're here because auth broke and you need to know why. Decoding the token you're actually sending is the fastest way to see what the server sees, no extra logging required. A few claims carry almost all the answers.
Start with exp. An expiry in the past means the token is dead and your refresh flow probably isn't kicking in, and that's the first thing to rule out on any 401. After that, iss (who issued it) and aud (who it's for) are the usual suspects. A token whose aud doesn't name your API gets rejected even though it decodes perfectly, and a stale iss tends to surface right after someone switches auth providers or environments. If your backend recently started checking a custom claim, a role or permissions or a tenant id, older tokens that predate that field fail with no obvious reason, and popping one open shows the field simply isn't there. That's the bug that looks like a mystery until you read the payload.
This tool reads the token but never checks the signature. Decoding just pulls apart the base64url content, which anyone holding the token can do, and it says nothing about whether the token is genuine. Verifying is the separate step that checks the signature against the signing secret or the issuer's public key, and that's what proves the token wasn't forged or edited after it was issued. A token can decode into clean, sensible JSON and still be expired or quietly tampered with. So what you see here is a debugging view of the claims. It won't tell you the token is real, and that check belongs on your backend, where the key actually lives.
Frequently asked questions
Some decoders let me edit the token and re-sign it. Why doesn't this one?
Because those are two different jobs, and mixing them invites mistakes. This tool is read-only on purpose. It shows you what a token contains and whether it's still valid, and it never re-encodes or re-signs anything. If you need to mint or tamper with a token for testing, use a dedicated signing library where you control the key, not a browser page. Keeping decode away from sign also means there's no key handling here at all, which is one less thing that can go wrong with a real credential.
My token has non-English characters in a claim and they show up correctly. How?
Plain base64 decoding mangles multi-byte characters, so a name with an accent or an emoji in a claim often comes back as garbage in simpler decoders. This one decodes as UTF-8, so José, 北京, or any other non-ASCII value reads back exactly as it was stored. Useful when a name or preferred_username claim isn't plain ASCII and you need to see what's really in it.
It says my token is expired but it still works on the server. What gives?
The validity check compares exp against your computer's clock, so if your device clock is off, or drifted, the verdict can be wrong even though the token is fine. Servers also tend to allow a small amount of clock skew, so a token a few seconds past exp locally may still pass on their end. Check your system time before you trust the expired flag.
Why won't my token decode at all?
A JWT is exactly three base64url parts split by dots. The common reasons it won't parse: a copy that dropped a segment, stray whitespace or quotes clinging to the string, a leftover Bearer prefix, or a value that isn't a JWT in the first place, like an opaque session id that only means something to the server that issued it. Re-copy the full token, trim anything around it, and try again.
It says the algorithm is RS256 (or none). Does that change how it reads?
Not at all. The alg field tells whoever verifies the token which math to run against the signature, and this decoder skips that step, so it reads a token no matter how it was signed. HS256, RS256, ES256, all identical on this end. You'll even get claims back from a token whose alg is literally none with an empty third segment, and that one is worth a second look, since a backend that actually accepts alg: none is a genuine security hole worth catching early.
Can someone read a JWT I hand them?
Yes, assume they can read every claim in it. A signed JWT hides nothing. The signature is only there to keep the token from being altered, and it does nothing to hide what's inside, so keep passwords, API keys, and personal data out of the payload, and don't paste real production tokens into sites you don't control. If the contents genuinely need to be hidden, that's a separate encrypted format called JWE, which the everyday signed JWT is not.