Decode and inspect JWT tokens instantly. View header, payload, and verify signatures. Security validation included. Free, works in your browser.
Paste a JSON Web Token into the box above and it splits at the dots, base64url–decodes the first two segments, and prints the header and payload as formatted JSON. Decoding starts as soon as text lands in the field — there is no submit button. Every timestamp claim (exp, nbf, iat) is rendered twice: the raw integer as it appears in the token, and the same value converted to a readable local date and time, so you can answer “when did this expire” without opening a converter.
All of it runs in your browser. The token is never sent to a server, never logged, and never stored. The one exception you should know about: the decode tab mirrors the token into the address bar as ?token= so the “Copy shareable link” button can hand someone the same view. That link carries the token in it. Decoding is still local, but the link itself is the token — do not paste a production access token and then share the URL. The URL is written with history replacement rather than a push, so tokens do not pile up in browser back–history.
This tool decodes and inspects. It does not verify signatures on the decode side. That distinction is not a limitation to work around — it is the whole point of a decoder. A JWT payload is base64, not ciphertext, so anyone holding the token can read it whether the signature is good or forged. Reading claims and trusting claims are separate acts, and only your backend, holding the key, can do the second one.
The seven registered claims from RFC 7519 are what the tool lists under “Standard Claims”. Everything else in the payload is shown separately as a custom claim. Here is what each one is actually for, and what breaks when it is wrong.
| Claim | Name | Type | What it does, and how it fails |
|---|---|---|---|
iss | Issuer | String (usually a URI) | Who minted the token. Verifiers reject a token whose iss is not on their allowlist. A trailing slash mismatch — https://auth.example.com versus https://auth.example.com/ — is a classic cause of “invalid issuer” on an otherwise perfect token. |
sub | Subject | String | Who the token is about: the user or service identity. Unique within the issuer, not globally. Treat it as opaque — do not parse it, and do not assume it is an email. |
aud | Audience | String or array of strings | Who the token is for. If your API rejects a token that decodes fine, check this first: a token issued for one API and presented to another is correctly refused. The array form is legal and common, and a verifier passes if any element matches. |
exp | Expiration time | NumericDate (integer seconds) | Instant after which the token must be rejected. Absent means the token never expires on its own, which the tool flags as a warning. |
nbf | Not before | NumericDate | Instant before which the token must be rejected. Usually equal to iat, but set into the future for tokens issued ahead of time. |
iat | Issued at | NumericDate | When the token was created. Used for token age policies and for spotting a client whose clock is wrong. |
jti | JWT ID | String | Unique identifier for this specific token. What a revocation list or replay–detection cache keys on. |
Header claims are a shorter list. alg names the signing algorithm, typ is conventionally JWT, and kid — the key ID — tells the verifier which key from a JWKS endpoint signed this token. When you are debugging a verification failure against a rotating key set, kid is the field to compare against the keys the verifier actually has.
Every NumericDate claim is seconds since 1970–01–01 UTC, not milliseconds. This is the single most common source of nonsense timestamps. If your decoded exp renders as a date tens of thousands of years in the future, the issuer emitted Date.now() from JavaScript without dividing by 1000. If it renders in early 1970, someone divided twice. Both are issuer bugs, not decoder bugs, and both are obvious the moment the integer is shown next to its human–readable form.
Once a token decodes, the tool computes a status panel from the payload’s time claims against your machine’s clock and shows one of four states:
exp is in the future and nbf, if present, has passed. The panel also shows time remaining, and escalates its warning level as expiry approaches.exp is in the past. The panel says how long ago, in seconds, minutes, hours or days.nbf is still in the future, so a correct verifier will refuse the token even though it has not expired. Almost always a clock–skew problem between issuer and consumer.exp claim at all. Flagged as a warning, because a bearer token with no expiry is valid until the signing key is rotated or the token is explicitly revoked.Alongside the status you get the absolute expiry time, the issue time, the not–before time, and token age computed from iat. When a token works on one machine and fails on another, compare token age here against the clock on the failing host — a verifier with a clock a few minutes fast will reject freshly issued tokens as not–yet–valid, and one running slow will accept tokens past their expiry.
A JWT signature covers exactly the first two segments joined by a dot: base64url(header) + "." + base64url(payload). The third segment is the signature over that byte string. Verification recomputes it and compares. So “signature verification failed” means one of a small number of concrete things, and the decoded header and payload above tell you which:
kid with the key IDs the verifier holds.Bearer, or URL–encoding applied to the token. If the tool above reports “Invalid JWT format. Expected 3 parts separated by dots.”, the token is damaged before verification is even reached.Two errors get confused with signature failure and are not it. “Token expired” means the signature verified perfectly and then exp failed — a valid signature over a stale token. “Invalid audience” or “invalid issuer” likewise happen after a successful signature check. If your error message names a claim, the cryptography was fine and the policy check was not.
alg: noneThe header declares which algorithm signed the token. That is metadata written by whoever produced the token, which in an attack means it is written by the attacker. Two classic exploits follow directly from a verifier that trusts it.
alg: none. RFC 7519 defines an unsecured JWT: algorithm none, empty signature segment, so the token ends with a trailing dot and nothing after it. A verifier that reads alg from the header and dispatches on it can be handed {"alg":"none"} with any payload the attacker likes and will accept it, because “verifying” a none token is a no–op. The tool flags any decoded token whose alg is none as critical for exactly this reason. The builder tab can produce unsigned tokens too — useful for testing whether your own verifier rejects them, which is the only reason to generate one.
RS256 to HS256 confusion. An attacker takes a service that signs with RS256, rewrites the header to HS256, and signs the token using the service’s public key as the HMAC secret. A verifier that dispatches on the header algorithm will fetch the public key it was configured with, use it as an HMAC key because the header said HMAC, and the signature will check out. The public key is public, so the attacker forges arbitrary tokens.
The fix for both is the same one line of configuration: pin the accepted algorithms at the verifier and never let the token choose. Pass an explicit allowlist — algorithms: ["RS256"] or the equivalent in your library — and reject anything else before any key is loaded.
The tool grades the header algorithm whenever a token decodes: the RSA, ECDSA and RSA–PSS families are marked as sound choices, the HMAC family is flagged as symmetric — every party that can verify a token can also mint one — none is flagged as critical, and anything unrecognised is flagged for review.
JWT segments are base64url–encoded (RFC 4648 §5), not standard base64. Two differences matter in practice:
- and _ instead of + and /. A plain base64 decoder either errors or silently produces garbage when it meets them.= padding is stripped. Standard base64 decoders often demand a length that is a multiple of four.To decode a segment by hand, translate - to + and _ to /, then append = until the length is a multiple of four. That is precisely what this tool does internally before decoding, which is why pasting a raw segment into a generic base64 decoder can fail on a token this page reads without complaint.
There is a second, subtler consequence: because + and / are absent, a JWT is safe in a URL query string and in a form field without further encoding. If you see %2B or %2F inside a token, something has base64–encoded a JWT a second time or URL–encoded standard base64 — either way the token you have is not the token that was issued.
Two conveniences beyond the spec. If the input is a single segment rather than three, it is decoded as plain base64 and pretty–printed when the result parses as JSON. And if a token arrives lowercased — by a logging pipeline, a case–insensitive database column, or a shell — the tool attempts case recovery, scoring candidate decodings chunk by chunk for text–likeness and reporting high, medium or low confidence along with the recovered base64. Base64 is case–sensitive, so this is reconstruction, not decoding: low confidence means go and get the original token.
The Build JWT tab creates and signs tokens in the browser using the jose library, loaded on demand only when you press generate. Nothing you type — secret, private key, or claims — leaves the page.
none for an unsigned token.iss, sub, aud and jti. A comma in the audience field produces the array form of aud. A button fills jti with a random 16–byte hex value from the browser’s crypto source.iat is set to now by default and can be switched off. exp takes a preset of one hour, one day, one week or thirty days, an explicit datetime, or none at all. nbf is off by default and takes a datetime.kid alongside the generated alg and typ.-----BEGIN PRIVATE KEY-----. A PKCS#1 key (BEGIN RSA PRIVATE KEY) is rejected; convert it to PKCS#8 first.A live preview shows the header and payload JSON as you edit, so you can see the claim set before committing to it. After generating, the token appears with its three encoded parts listed separately and individually copyable, and one button loads it straight into the decode tab — the quickest way to confirm that a claim landed where you meant it to.
Bearer prefix is part of the HTTP Authorization header, not part of the token. Paste only what follows the space.aud, iss, or a rotated key.aud claim is what tells them apart.Use a verifying library, not a decoder, whenever the answer affects an authorisation decision — that means your language’s JWT library with an explicit algorithm allowlist, the issuer’s JWKS endpoint, and expected iss and aud values. Use a JWE library if the claims themselves need confidentiality. If your string is not a JWT at all — no dots, or segments that will not decode to JSON — a general encoding tool such as the Base64 encoder and decoder is the better place to start, since it auto–detects hex, Base32, URL encoding and other formats that get mistaken for tokens.
JSON Web Tokens (JWTs) are used for authentication and authorization in modern web applications. This tool decodes JWTs to show their header, payload, and signature.
JSON Web Tokens (JWTs) are a compact, URL-safe way to represent claims between two parties. They're widely used for authentication and authorization in modern web applications.
Every JWT consists of three parts separated by dots (.):
JWT) and the signing algorithm (e.g., HS256, RS256)xxxxx.yyyyy.zzzzz
Header.Payload.Signature
JWTs use standardized claims to convey information:
| Claim | Full Name | Purpose |
|---|---|---|
iss | Issuer | Who created the token |
sub | Subject | Who the token refers to (usually user ID) |
exp | Expiration | When the token expires (Unix timestamp) |
iat | Issued At | When the token was created |
aud | Audience | Intended recipient of the token |
nbf | Not Before | Token is not valid before this time |
exp) to prevent replay attacksJWTs work best for:
Avoid JWTs for:
Yes. This decoder runs entirely in your browser using JavaScript — when you paste a token it is split, Base64URL-decoded, and parsed locally on your device. The token is never sent to our servers, never logged, and never stored, so it is safe to inspect tokens that contain real user data while debugging. You can confirm this yourself by opening your browser's network tab while decoding: no request is made. The usual caveat for any online decoder still applies — only paste production tokens into tools that are explicitly client-side, which this one is.
For reading a token, the decoder displays the algorithm from the header for every standard type — the HMAC family (HS256, HS384, HS512), RSA (RS256, RS384, RS512), ECDSA (ES256, ES384, ES512), RSA-PSS (PS256, PS384, PS512), and the unsecured "none" algorithm. Decoding works regardless of algorithm because reading the header and payload only requires Base64URL decoding, not the signing key. The algorithm field matters most when you move on to verifying or signing a token. For guidance on which algorithms are actually secure to use, see the linked deep-dive.
Often, yes. Base64URL is case-sensitive, so a token that was forced to lowercase somewhere along the way (some logging pipelines, URL handlers, or databases do this) is normally corrupt and will not decode. This tool includes a lowercase-base64 recovery step: when it detects an all-lowercase segment, it searches for the case combination that decodes to valid JSON and flags that the input was recovered. It is a best-effort heuristic — long random claim values may not be fully recoverable — but it rescues many tokens that other decoders reject outright. This recovery feature is unique to this tool and is not something the blog articles cover.
Decoding and verification are two different operations, and the decode view does exactly one of them: it reads the token. It shows you the header, payload, claims, and expiration status, but reading a token does not prove it is authentic or untampered — anyone can decode any JWT without a key. Treat decoded claims as untrusted input. To confirm a token genuinely came from your auth server and was not modified, you must verify the signature with the secret or public key on the server side. The linked guide walks through signature verification end to end.
Yes. Switch from the Decode tab to the Build tab to construct a token from scratch: set the header algorithm, add registered claims (iss, sub, aud, exp, iat) and your own custom claims, choose an expiration preset, and supply a secret (for HMAC) or a PEM private key (for RSA/ECDSA/PSS) to produce a signed token. Signing happens locally in the browser. This is handy for generating test tokens when debugging an auth flow, so you do not have to paste a real production token to reproduce an issue.
The decoder labels each claim it finds, but if you want the background on what registered claims like iss, sub, aud, exp, iat, nbf, and jti represent — and how custom public and private claims differ — read the dedicated claims explainer. Understanding the claim set is what lets you tell, at a glance in the decoded output, whether a token is scoped to the right audience or carries the roles your app expects.
After you paste a token, the decoder reads the exp claim (a Unix timestamp in seconds) and shows the token's expiration status — valid, expired, or not-yet-valid (nbf) — so you do not have to do the timestamp math by hand. Remember that an expired token shown here is informational only: enforcement still happens server-side when the token is verified. For how the exp claim works and how refresh strategies are built around it, see the expiration deep-dive.
A JWT is three Base64URL segments joined by dots: header.payload.signature. This tool decodes and displays the header (algorithm and token type) and the payload (your claims) as readable JSON, and shows the signature as the raw third segment because a signature is a cryptographic hash, not encoded data — it cannot be turned back into readable text, only verified. If a string you paste does not split into exactly three non-empty parts, it is not a well-formed JWT and the decoder will tell you so. If you are deciding whether JWTs are even the right token format for your use case, the linked article covers when to use them.