JWT Decoder

Paste a JSON Web Token to read its header and payload, see every claim pretty-printed, and find out exactly when it was issued and when it expires. Decoding happens in your browser — the token is never sent anywhere, and the signature is never verified.

This tool decodes only. It does not verify the signature — that requires the signing secret or public key and must be done on your server. A token with a forged signature will decode here exactly like a genuine one.

No token decoded yet.
Paste a JWT above and press Decode to see its header and payload.

What a JWT actually is

A JSON Web Token is a compact, self-contained way to carry a set of statements — called claims — between two parties. Written out, it is three chunks of Base64url text joined by dots: header.payload.signature. The header is a small JSON object naming the signing algorithm and the token type. The payload is another JSON object holding the claims: who the token is about, who issued it, when it expires, and whatever application-specific data the issuer chose to include. The signature is a cryptographic value computed over the first two segments together with a key.

The point of that structure is that a recipient can validate the token without a database lookup. If the signature checks out against the expected key, the payload has not been altered since it was issued, so the server can trust the user ID and permissions inside it and get on with the request. That property is what makes JWTs so common in stateless APIs and single sign-on flows — and it is also the source of every one of their downsides, because a statement you can verify offline is a statement you cannot easily retract.

Decoding is not verifying — and it never will be

This is the single most important thing to understand about any online JWT decoder, including this one. Reading a token requires nothing but a Base64 decoder. Verifying it requires a secret. For an HS256 token the verifier needs the shared HMAC secret; for RS256 or ES256 it needs the issuer's public key, usually fetched from a JWKS endpoint. A browser page has none of that, so this tool deliberately makes no claim about authenticity. A token with a completely fabricated signature will decode and display exactly like a genuine one. Treat what you see here as "what this token says", never as "what this token proves".

There is a second reason this page will never offer to verify: doing so would mean asking you to paste your signing secret into a web form. No online tool should ask for that, and you should not give it to one. Verification belongs in your own server process, using a library that you have configured with the exact algorithm and key you expect.

A JWT payload is not encrypted — here is the proof

Base64url is an encoding. It exists to make arbitrary bytes safe to put in a URL, not to hide them. There is no key involved, so reversing it takes no privilege whatsoever. Consider this payload:

{"sub":"user_10923","email":"ada@example.com","password":"hunter2","role":"admin"}

Base64url-encoded, that becomes the middle segment of a token, and it looks satisfyingly opaque:

eyJzdWIiOiJ1c2VyXzEwOTIzIiwiZW1haWwiOiJhZGFAZXhhbXBsZS5jb20iLCJwYXNzd29yZCI6Imh1bnRlcjIiLCJyb2xlIjoiYWRtaW4ifQ

It is not opaque at all. Paste that string into the Base64 decoder in URL-safe mode and the original JSON comes straight back, password and all — no key, no secret, no cracking. Anyone who can see the token can see the payload: the user's browser, any browser extension, anything reading the Authorization header, a proxy log, an error report, a screenshot in a support ticket. The signature stops the payload being changed. It does nothing to stop it being read.

So the rule is simple: a JWT payload is public data. Passwords, full card numbers, government identifiers, internal API keys, private notes and anything you would not print on a postcard do not belong in it. Put an opaque user ID and the minimum set of claims your API needs to authorise the request. If you genuinely need confidentiality rather than integrity, the JWE encrypted-token format exists for exactly that, and it is a different thing from the signed JWS tokens almost everyone means when they say "JWT".

Every claim, explained

Registered payload claims

These seven are defined by RFC 7519. All are optional, all are three letters, and every one of them is a thing your verifier should be checking rather than merely displaying.

ClaimNameTypeWhat it means and what to check
issIssuerstring Who created and signed the token, usually the auth server's URL. Pin the exact expected value — accepting any issuer means accepting tokens minted by anyone whose key you happen to trust.
subSubjectstring Who the token is about, typically an opaque user ID. Unique within the issuer, not globally, so key your users on the iss and sub pair rather than sub alone.
audAudiencestring or array Which API the token is intended for. Reject tokens addressed elsewhere — otherwise a token issued for a low-trust service can be replayed against a high-trust one.
expExpirationnumber (seconds) Unix time after which the token must be rejected. Required in practice even though the spec makes it optional; a token without exp is valid forever.
nbfNot beforenumber (seconds) Unix time before which the token must be rejected. Used for tokens issued ahead of time; a common cause of confusing failures when clocks drift.
iatIssued atnumber (seconds) When the token was created. Useful for enforcing a maximum age independently of exp, and for detecting tokens issued before a credential change.
jtiJWT IDstring A unique identifier for this token, conventionally a UUID. This is the handle a denylist uses to revoke one specific token, and the key a replay check deduplicates on.

All the time-based claims are seconds since the Unix epoch, not milliseconds. Mixing the two up is a classic bug: a millisecond exp value lands roughly fifty thousand years in the future, so the token effectively never expires and no error is ever raised to tell you. The decoder above renders every timestamp claim in both UTC and your local timezone precisely so this stands out.

Common non-standard claims

Beyond the registered seven, identity providers add their own. These are the ones you will meet most often — but check your provider's documentation, because none of them are guaranteed.

ClaimUsuallyNotes
scopeSpace-separated string OAuth 2.0 scopes such as read:orders write:orders. Note the singular name and the space delimiter — scopes as an array is a common provider-specific variation, and mixing them up produces authorisation checks that silently always fail.
rolesArray of strings Application roles. Entirely non-standard; Auth0 namespaces it, Keycloak nests it under realm_access.roles, Azure AD uses roles at the top level. Never assume the shape.
emailString From OpenID Connect, and only meaningful together with email_verified. Treating an unverified email claim as an account identifier is a well-known account-takeover path.
azpString Authorized party — the OAuth client ID the token was issued to, as distinct from the audience it is addressed to. Check it when one API serves several client applications with different privileges.
client_idString The OAuth client, in providers that follow RFC 9068 for access tokens rather than using azp.
auth_timeNumber (seconds) When the user actually authenticated, which can be much earlier than iat if the session was silently refreshed. Check it before allowing sensitive operations.
nonceString Echoes the value your client sent in an OpenID Connect authorization request, binding the ID token to that specific login attempt.

Header parameters

ParameterMeaning
algThe signing algorithm, for example HS256, RS256 or ES256. Informational only — your verifier must pin the algorithm it expects rather than obeying this field.
typMedia type of the token, almost always JWT. RFC 9068 recommends at+jwt for OAuth access tokens so they cannot be confused with ID tokens.
kidKey ID — tells the verifier which key from the issuer's JWKS to use. Essential for key rotation. Never fetch a key from a URL supplied inside the token itself.
ctyContent type of the payload, used when a JWT is nested inside another JWT.
jku / x5uURLs pointing at the key set or certificate. Seeing these in a token you receive is a red flag: a verifier that follows them can be pointed at an attacker's key.

Algorithms: HS256 vs RS256 vs ES256

The alg value in the header names how the signature was produced. The three you will meet almost every time split into two very different families.

AlgorithmFamilyKey materialSignature sizeUse when
HS256Symmetric — HMAC-SHA256One shared secret, ≥256 bits32 bytesThe same party issues and consumes the token
RS256Asymmetric — RSASSA-PKCS1-v1_5 + SHA-256RSA private/public pair, ≥2048-bit256 bytesMany verifiers, broad library support required
PS256Asymmetric — RSASSA-PSS + SHA-256Same RSA pair256 bytesYou want RSA with the modern, provably-secure padding
ES256Asymmetric — ECDSA on P-256 + SHA-256EC private/public pair64 bytesSize matters and your stack supports EC keys
EdDSAAsymmetric — Ed25519Ed25519 pair64 bytesModern greenfield systems; fastest and hardest to misuse
noneNo signature at allNone0 bytesNever, in anything that faces a network

Symmetric: one secret does both jobs

HS256 computes an HMAC over header.payload using a shared secret. It is fast, the signature is small, and every language has it built in. The catch is structural: the key that verifies is the key that signs. Any service you give the secret to so it can validate tokens can also mint tokens claiming to be anyone. In a monolith where one process issues and consumes its own session tokens, that is a non-issue and HS256 is a perfectly good choice. In a microservice estate, it means distributing a forging key to a dozen deployments, which is exactly the situation asymmetric signing was invented to avoid.

A second HS256 hazard is key strength. The secret is just a byte string, so nothing stops someone using secret or the application name. An HMAC signature is offline-crackable: an attacker with one captured token can grind candidate secrets on a GPU until the signature matches, then forge tokens forever. Use at least 256 bits from a cryptographic random source — the UUID generator on this site is a convenient way to get random bytes if you have nothing better to hand, though a dedicated secret manager is the right long-term answer.

Asymmetric: the verifier cannot forge

RS256 and ES256 sign with a private key held only by the issuer and verify with a public key that can be published to the world. This is what makes federated identity work: your API downloads the identity provider's JWKS document, caches it, and validates tokens forever without ever holding anything sensitive. A compromised resource server leaks no ability to issue tokens.

Between the two, ES256 gives equivalent security with dramatically smaller keys and signatures — 64 bytes against RSA's 256. That matters when tokens travel in headers on every request, because a fat token eats into proxy header limits and can turn into a 431 Request Header Fields Too Large. RS256 remains the most widely supported and is still the sensible default when you cannot control every consumer's library. PS256 and EdDSA are better cryptography; use them when your whole stack agrees.

Why alg: none is an attack class, not a curiosity

The JWT specification defines none as a valid algorithm for unsecured tokens, and the header that declares it is supplied by whoever holds the token. That combination is the flaw. An attacker takes a real token, rewrites the payload to {"sub":"admin"}, sets the header to {"alg":"none"}, drops the signature segment and leaves a trailing dot. A library that reads the algorithm out of the token and dispatches on it will conclude that no signature is required and accept the forgery. This was not hypothetical: it was found in a long list of popular JWT libraries in 2015 and it still resurfaces.

The closely related algorithm confusion attack targets asymmetric setups. The attacker takes a server's public RSA key — which is public by design — rewrites the header from RS256 to HS256, and signs the forged token using that public key as the HMAC secret. A verifier that trusts the header calls the HMAC path with the RSA public key as the secret, the signature matches, and the token is accepted.

Both are fixed the same way, and it is the single most important line of code in JWT handling: pin the expected algorithm in the verification call. Pass algorithms: ['RS256'] — an explicit allowlist — rather than letting the library infer it. Reject none unconditionally. If the decoder above ever shows you alg: none in a token you received in production, treat it as an incident, not a quirk; it flags this case explicitly for that reason.

Using JWTs safely

Keep the payload public-safe and small

Covered above, but worth restating as a rule: assume every field in the payload will be read by someone you did not intend. Beyond secrecy there is a size argument. Tokens travel in the Authorization header on every single request, and many proxies cap total header size at 4–8 KB. Serialising a whole user profile, a permission matrix or an avatar URL into the payload produces a token that works in development and fails behind the load balancer with a 431 or an opaque 400. Carry an identifier and let the API look up the rest.

Token lifetime

Lifetime is your primary damage-control lever, because it is the one thing that limits how long a stolen token is useful. The conventional split:

  • Access tokens: 5 to 15 minutes. Short enough that a leaked token expires before most attackers act on it, long enough that refreshes are not constant.
  • ID tokens: minutes. They are consumed once at login to establish a session, not presented repeatedly.
  • Refresh tokens: days to weeks, stored server-side and revocable, ideally with rotation — each use issues a new refresh token and invalidates the old one, so a replayed refresh token is detectable evidence of theft and can kill the whole family.
  • Never "no expiry". A token without exp is a permanent credential you have no way to withdraw.

Allow a small clock skew — 30 to 60 seconds is typical — when checking exp and nbf. Servers whose clocks are a few seconds apart otherwise reject perfectly good tokens, and the resulting 401s are miserable to diagnose.

Storage: localStorage vs httpOnly cookie

There is no option here that is safe against everything; there is a trade you have to make consciously.

httpOnly cookielocalStorage / sessionStorage
Readable by JavaScriptNoYes
Exposure to XSSToken cannot be exfiltrated, but the attacker can still make authenticated requests from the pageToken is read and exfiltrated in one line
Exposure to CSRFYes — needs SameSite and usually a CSRF tokenNo — nothing is sent automatically
Cross-origin API callsAwkward: needs CORS credentials and SameSite=None; SecureEasy: attach the header yourself
Survives a page reloadYesYes (localStorage) / no (sessionStorage)
Mobile and native clientsPoor fitUse the platform keychain instead

For a browser application talking to its own backend, an httpOnly; Secure; SameSite=Lax cookie is the better default: it removes the token-theft class of XSS outcome entirely, and CSRF is a well-understood problem with a mechanical fix. For a single-page app calling an API on a different origin, or any non-browser client, header-based tokens are the pragmatic choice — pair them with a strict Content Security Policy and keep the lifetime short. And note the honest caveat in the table: if an attacker has script execution on your page, they can act as the user either way. Fixing XSS is not optional groundwork for this decision, it is the decision that matters most.

Revocation is the hard problem

The whole appeal of a JWT is that verification needs no shared state. The direct consequence is that there is no state to change when you want a token to stop working. Logging a user out clears their browser; it does nothing to a copy of the token an attacker already holds. Deleting the user account does not stop a token bearing their ID from validating until exp. Changing a password does not either, unless you build something that makes it.

The workable approaches, roughly in order of cost:

  • Short lifetimes. Not revocation, but it bounds the window to minutes and covers most real scenarios. This is why it is the first line of defence rather than an optimisation.
  • A jti denylist. Store revoked token IDs in a fast cache with a TTL matching the token's remaining life, and check it on every request. Cheap because the list is naturally small, but it reintroduces the stateful lookup JWTs were meant to remove.
  • A per-user token version. Keep an integer on the user record, include it as a claim, and increment it on password change or forced logout. One lookup per request, but it invalidates every token for that user at once — the right tool for "log me out everywhere".
  • Refresh token rotation. Keep access tokens short and un-revocable, and make refresh tokens stateful, single-use and revocable. Reuse of a rotated refresh token is a strong theft signal.
  • Opaque tokens with introspection. If you need immediate revocation on every request, a random token checked against a store is the honest design. Accepting that a JWT is the wrong tool is sometimes the correct engineering answer.

Whichever you pick, decide it before launch. Retrofitting revocation onto a system with twenty-four-hour access tokens during an active incident is not a good day.

Troubleshooting: what the failure means

Most JWT problems reduce to a small set of causes. This table maps the symptom to the check, and to the response your API should be returning.

SymptomLikely causeCorrect response
Token decodes fine, API rejects itexp has passed, or the clock is skewed 401 with WWW-Authenticate: Bearer error="invalid_token"
Works against one service, fails on anotheraud does not match the second service 401 Unauthorized
"Invalid signature" from the libraryWrong key, wrong kid, or a rotated JWKS not refetched 401 Unauthorized
Works in staging, fails in productioniss points at the staging authorization server 401 Unauthorized
Authenticates, then still refusedIdentity is fine; the required scope or role is absent 403 Forbidden — do not use 401 here
"Malformed token" / segment count errorThe Bearer prefix or a line break was included 400 Bad Request
Token rejected as not yet validnbf is in the future relative to the server clock 401 Unauthorized
Request fails before reaching your codeToken grew too large for the proxy's header limit 431 Request Header Fields Too Large

The 401-versus-403 distinction is the one teams get wrong most often, and it matters because clients behave differently: a 401 should prompt a refresh-and-retry, while a 403 should not, because retrying with the same identity will never succeed. Use 401 when you do not know who the caller is — no token, expired token, bad signature — and 403 when you know exactly who they are and they are not allowed. Reproduce the failing call on the API tester and paste the same token in here to compare what the token says with what the API expected.

When you reach for a JWT decoder

Debugging a 401 that should have worked

You send a request, the API answers 401 Unauthorized, and the token looks fine. Decoding it usually settles the question in seconds: the exp has passed, the aud points at a different service, the iss is your staging auth server rather than production, or a required scope is simply absent from the payload.

Checking what a third party actually issued

When you integrate an identity provider, the claim names in the documentation and the claim names in the real token do not always agree. Decoding a live token tells you what you are actually receiving — including custom namespaced claims that the docs often omit entirely, and the exact shape of roles or scope that your authorisation check has to parse.

Confirming your own token minting

If you issue tokens, decoding one after a deploy is a fast sanity check that the algorithm in the header is what you intended, that kid is present when you rotate keys, that exp is in seconds and the lifetime is what you configured, and that you have not accidentally serialised a whole user record into the payload. Pretty-print the result with the JSON formatter if you want to diff two tokens side by side.

Common pitfalls

  • Copying the Authorization header, not the token. The header value is Bearer eyJ.... The word Bearer and the space are not part of the JWT. This decoder strips a leading Bearer for you, but many libraries will not.
  • Whitespace and line breaks. Tokens copied out of logs or terminals often arrive wrapped. Any stray whitespace inside the token breaks Base64 decoding.
  • Confusing Base64 with Base64url. JWT segments use - and _ in place of + and /, and drop the trailing = padding. A plain Base64 decoder may reject a segment that is perfectly valid; use its URL-safe mode.
  • Clock skew. Expiry here is judged against your own machine's clock. A workstation several minutes out of sync will disagree with the server.
  • Trusting alg from the token. A server must pin the expected algorithm rather than reading it from the header, or an attacker can present alg: none or swap RS256 for HS256 and skip verification entirely.
  • Not validating aud and iss. A signature check alone only proves the token came from a key you trust — not that it was meant for you. Both must be pinned.
  • Assuming revocation. A signed, unexpired token stays valid until it expires. Logging a user out does not invalidate a JWT unless you maintain a denylist or token version.
  • Putting a JWT in a URL. Query strings land in server logs, browser history and Referer headers. Tokens belong in a header or an httpOnly cookie — see the HTTP headers guide.

Frequently asked questions

Does this decoder verify the signature?

No. Signature verification needs the HMAC secret or the issuer's public key, which a browser tool has no safe way to hold — and no online tool should ever ask you for your signing secret. This page decodes the header and payload only. Verify on the server, and pin the algorithm you expect rather than reading it from the token.

Is my token uploaded anywhere?

No. Everything happens in JavaScript on this page. Open your browser's network tab while decoding — there are no requests. The token is not logged, stored or transmitted.

Is a JWT encrypted?

A standard signed JWT is not. Base64url is reversible by anyone with no key at all — paste the middle segment into a Base64 decoder and you get the JSON straight back. The signature protects the payload from tampering, not from being read. Keep secrets out of the payload; use JWE if you need real confidentiality.

What is the difference between HS256, RS256 and ES256?

HS256 is symmetric: one shared secret both signs and verifies, so every verifier can also forge. RS256 and ES256 are asymmetric: a private key signs, a public key verifies, so a resource server can validate without being able to issue. ES256 gives equivalent strength to RS256 with a 64-byte signature instead of 256 bytes.

Why is alg: none dangerous?

Because the algorithm is declared by whoever holds the token. A library that dispatches on the header will accept a token that says none with an empty signature, skipping verification entirely. The variant attack rewrites RS256 to HS256 and signs with the public key. Both are fixed by passing an explicit algorithm allowlist to your verifier.

Why does the tool say expired when my API accepts the token?

Expiry is evaluated against your device clock. Servers also commonly allow a small skew allowance of thirty to sixty seconds. Check your system time, and check that exp is in seconds rather than milliseconds, before concluding the token is at fault.

Should I store a JWT in localStorage or a cookie?

An httpOnly, Secure, SameSite cookie is the safer default because script cannot read it, so XSS cannot exfiltrate the token — at the cost of needing CSRF protection. localStorage avoids CSRF and suits cross-origin APIs but is readable by any injected script. If you have XSS, neither saves you; fix that first.

How do I revoke a JWT?

Mostly you cannot, which is the format's central trade-off. Use short access-token lifetimes with revocable rotating refresh tokens, a denylist keyed on jti, or a per-user token version you bump on password change. If you need instant revocation on every request, an opaque token checked against a store is the more honest design.

Can I decode a token that has no signature segment?

Yes. Unsigned tokens use alg: none and end with a trailing dot and an empty third segment. They decode fine here and the tool flags them, but no production API should ever accept one.

Related tools and reference