JWT Decoder

Replaces the current input with demonstration data.

Expected result: The header shows HS256 and the payload shows sub = demo-user. The signature is deliberately invalid; this is not a login token.

Limitations: Decoding does not authenticate a token. Optional signature checks support HS256/384/512; applications must also enforce issuer, audience, and time claims.

Decoding is not verifying. Anyone can read a JWT payload and anyone can forge one that decodes cleanly — a decoded token shows what it claims, never whether those claims are true. Everything here runs in your browser; your token is never sent anywhere.

Nothing you enter here is stored, logged or shared.

Paste a JWT to read its header and payload and see when it expires. Decoding happens entirely in your browser, so the token never leaves your device.

How to use

  1. Paste the token into the input field.
  2. The header, payload and time claims appear below.
  3. Optionally enter the secret to verify an HS256 or HS512 signature.

Reading the three segments

A JWT is three base64url-encoded segments joined by dots, and base64url here is written without padding, which is why the segments do not end in equals signs the way ordinary base64 does.

The first segment is the header: a small JSON object naming the signing algorithm in alg, usually the type in typ, and sometimes a key identifier in kid so the verifier knows which of several keys to check against. The second is the payload, a JSON object of claims. The third is the signature, which is raw bytes rather than JSON and so is shown here as hexadecimal.

The signature covers the first two segments exactly as they appear, joined by a dot - the encoded text, not the JSON it represents. This is why a token cannot be prettified, re-serialised, or have its claims reordered without invalidating it, and why the decoder shows you the claims while treating the original text as the thing that matters.

Decoding is not verifying

This is the point on which JWTs are most often misunderstood, so it is worth stating without hedging. The payload is encoded, not encrypted. Anyone holding the token can read every claim in it. Anyone at all can construct a token whose payload says whatever they like, and it will decode perfectly here and everywhere else.

What a decoded token gives you is a set of assertions and no evidence for any of them. The evidence is the signature, and checking it requires the key. Until that check has happened - against a key you chose in advance, using an algorithm you chose in advance - a token tells you what someone wants you to believe.

The practical version of this: never make an authorisation decision from a decoded payload. Reading a token in a debugger to see what is in it is fine. Reading the sub claim in application code without verifying, because the token came through a gateway that was supposed to have checked it, is how services end up trusting forged identities. Optional HS256 verification is offered on this page precisely so the difference is visible.

The claims, and who is responsible for checking them

RFC 7519 registers seven claims. iss is the issuer, sub the subject the token is about, aud the audience it is intended for, and jti a unique identifier for the token itself. The three time claims - exp, nbf and iat - are NumericDate values in seconds since the Unix epoch, not milliseconds, which is the most common reason a hand-built token is rejected.

None of these enforce themselves. A token with an exp in the past is expired only because a verifier compares it against the clock and refuses. A token issued for a different audience is dangerous only if nobody checks aud - which is how a token minted for one service gets replayed against another that shares the same signing key. Most libraries check exp by default and aud only if you tell them what to expect.

Small clock differences between the issuing and verifying machines are normal, so verifiers usually allow a skew tolerance of a minute or two. This page shows each time claim as a readable date and says whether the token is currently within its validity window.

When a JWT is the wrong choice

A JWT is a bearer token that carries its own claims, which makes it excellent for stateless service-to-service calls and awkward as a session mechanism.

The awkwardness is revocation. Because the verifier needs nothing but the key, there is no point at which it consults a store, and therefore no way to invalidate a token before its exp. Log a user out, change their password, disable their account: any token already issued keeps working until it expires. The usual answers - very short lifetimes with refresh tokens, or a deny-list of jti values checked on every request - both reintroduce the state that statelessness was supposed to remove.

Where they live matters too. A token in localStorage is readable by any script that runs on the page, so a single cross-site scripting flaw exfiltrates it; an httpOnly cookie is not, at the cost of needing CSRF protection. And because they travel in a header on every request, large payloads have a real cost - stuff enough claims in and you will meet a proxy's header size limit.

For a plain web session, a random opaque identifier against a server-side store is simpler, revocable instantly, and smaller.

What verification does, in order

A correct verification is a sequence, and skipping any step is where the known attacks live. First, decide the algorithm in advance and reject any token whose header disagrees - never read alg from the token and trust it. Second, select the key: for a shared secret that is a constant, for an asymmetric token it is the public key matching the kid in the header, fetched from the issuer's JWKS endpoint and cached.

Third, recompute the signature over the first two segments exactly as they appear and compare in constant time. Fourth, and only after the signature holds, read the claims: check exp and nbf against the clock with a small tolerance, check iss is the issuer you expect, and check aud contains you.

Most libraries do all of this if you configure them, and most vulnerabilities come from an application doing part of it by hand. Decoding to read a claim before verification is fine in a debugger and dangerous in request handling, because at that point the token is still just a claim somebody made.

Where JWTs sit in OAuth and OpenID Connect

In OpenID Connect, the id_token is a JWT and it is meant for the client: it says who signed in, when, and how. The access token is a separate thing meant for the API, and although many providers issue it as a JWT, the specification does not require that - it can be an opaque string the API validates by introspection.

The distinction matters because it decides what your API should do. If the access token is opaque, the API calls the provider's introspection endpoint; if it is a JWT, the API verifies it locally against the published keys. Treating an id_token as an access token is a common integration bug: it was issued for a different audience, and an API that accepts it will accept tokens minted for any application the same provider serves.

Scopes and roles inside a token are assertions from the issuer, not permissions. The API still decides what a caller may do; the token only says who they are and what the issuer was willing to vouch for.

The vulnerabilities that keep recurring

The alg: none trick declares a token unsigned and was accepted by several libraries a decade ago. Algorithm confusion is subtler: take an RS256 token, change the header to HS256, and sign it with the issuer's public key as the HMAC secret - a verifier that reads the algorithm from the token will accept it, because the public key is not a secret.

Weak shared secrets are the most common real-world finding. A JWT contains everything needed to test a candidate secret offline, at whatever rate the attacker's hardware allows, so a dictionary word falls in seconds and lets anyone mint tokens with any claims they like.

Then there are the logic errors: missing aud checks that let a token from one service authenticate to another sharing the same key; kid parameters used to load a key from a path without sanitising the input; and tokens accepted from a query string, where they end up in server logs, browser history and referrer headers.

Sessions or tokens for a web application

For a single web application with its own backend, a server-side session with an opaque cookie identifier is simpler and better in almost every dimension: revocation is immediate, the cookie is small, the contents never leave the server, and there is no signing to get wrong. The cost is a session store, which for most applications is a table or a Redis key.

JWTs earn their place where the verifier cannot consult the issuer: service-to-service calls across a trust boundary, an API used by third parties, or a stateless edge that must validate requests without a round trip. Those are real constraints and worth the complexity when you have them.

The hybrid most teams land on is a short-lived access token - minutes, not hours - with a longer-lived refresh token kept in an httpOnly cookie and stored server-side so it can be revoked. That buys stateless verification for the common path while keeping a place to say no.

Where to keep a token in a browser

localStorage is readable by any script running on the page, so a single cross-site scripting flaw anywhere on the origin - including in a third-party analytics or advertising script - exfiltrates the token. This is the default in many tutorials and is the weakest of the available options.

An httpOnly, Secure, SameSite cookie cannot be read by script at all, which removes that class of attack and introduces the need for CSRF protection, since the browser attaches the cookie automatically. SameSite=Lax handles most of it and an anti-forgery token covers the rest.

Keeping the token in memory only - a variable in the application, refreshed on page load through a cookie-authenticated endpoint - is the strongest common pattern and the least convenient, since a reload requires a refresh round trip. Whichever you choose, the token still travels in a header on every request, so keeping the payload small is a performance decision as well as a privacy one.

Frequently asked questions

Does decoding a JWT tell me it is valid?
No, and this is the most important thing to understand about JWTs. The payload is base64url encoded, not encrypted or protected: anyone can read it, and anyone can craft a token that decodes perfectly. Only verifying the signature against the right key proves a token is authentic and unaltered.
Is it safe to paste a real token here?
Decoding and verification both run in JavaScript on this page. Nothing is uploaded, logged or transmitted. That said, treat any JWT as a live credential: if it has leaked somewhere else, revoke it rather than relying on where you pasted it.
Why can only HS256 and HS512 be verified?
They are symmetric: the same secret both signs and verifies, so the check can run locally with what you provide. RS, ES and PS are asymmetric and need the issuer public key, usually fetched from a JWKS endpoint, which is a different job.
What do exp, iat and nbf mean?
They are registered claims holding a NumericDate, measured in seconds since the Unix epoch rather than milliseconds. exp is when the token stops being valid, iat when it was issued and nbf the earliest moment it may be used. This tool shows each one as a readable date.
What does alg: none mean?
It declares an unsigned token. Libraries that honour it can be tricked into accepting forged tokens, so it is a known attack pattern and should be rejected by any verifier. This tool will not report such a token as verified.

Last updated:

References

RFC 7519 — JSON Web Token

Recent changes

  • — Added a runnable example, expected results, limitations, and reference links.

Report a problem

Include the steps, expected result, and actual result. Remove secrets and personal data.

Related tools

Related articles