Nifty Tools

jwt decoder

Use a free jwt decoder in your browser to read JWT header and payload. Base64URL decode, exp and iat highlighted, browser-local. No signature verification.

Processing mode: Local Browser-local

Decode only. This page reads the JWT header and payload. It does not verify the signature — decoding a token does not prove it is authentic or unexpired in the eyes of an issuer.

Paste a JWT to decode the header and payload locally.

How to use it

JWT Decoder — Free, In Your Browser

  1. Paste the JWT into the input panel. A bare token (`eyJhbGciOi...`) and a full bearer header (`Authorization: Bearer eyJ...`) are both accepted; the page strips the `Authorization: Bearer ` prefix and any surrounding whitespace or quotes before processing.
  2. The page splits the input on `.` into header, payload, and signature, runs Base64URL decoding with missing-padding repair on the first two segments, walks the bytes through `TextDecoder("utf-8", { fatal: true })`, and pretty-prints the JSON with `JSON.parse`. Standard claims are highlighted, expiry is rendered against the local browser clock, and the `alg` and `kid` header values are surfaced as chips. The signature segment is shown as bytes only and is **never** treated as verified.
  3. Copy the decoded header or payload as JSON, or use the link to send the payload to [JSON formatter](/json-formatter/) for further indenting, or to [JSON diff](/json-diff/) to compare two tokens side-by-side. Nothing leaves your browser.

Good for

Common use cases

People reach for a jwt decoder when a token landed in front of them — in a network capture, an `Authorization: Bearer` header copied out of DevTools, a cookie pulled from a request, a webhook signature value, or a Slack message with the words "is this token still valid?" attached — and the readable claims are the only thing that helps debug what the token actually represents. The classic situations are a production OAuth bug where the API rejected a token and the question is "was this token already expired by the time the client sent it" (the `exp` claim, read as a local timestamp, answers that without going to the issuer), an API integration where the question is "what scopes does this token actually carry" (the `scope` or `permissions` claim, read straight off the payload, answers that without rerunning the auth flow), an audience-mismatch debug where the API returned 401 with an opaque error and the question is "did the token's `aud` match the one this API expects" (the `aud` claim, surfaced as a readable string, ends the speculation), an issuer rotation where the `kid` in the header has to match a key on the JWKS endpoint, an `nbf` (not-before) claim being slightly off on a clock-skew bug, or simply a JWT pasted into a debugging note where the question is "what is this token even for?" The same pipeline gets called JWT decoding, JWT parsing, JWT debugging, or "JWT decoder online" depending on which tool wrote the docs you read; they all describe the same operation — split the dot-separated JWT, Base64URL-decode the header and payload, and surface the resulting JSON. This page does that operation locally with the browser's native `atob` (over a `-`/`_` → `+`/`/` rewrite that maps the URL-safe alphabet back to standard Base64), `TextDecoder` in fatal mode (so a corrupt token surfaces honestly instead of becoming U+FFFD soup), and `JSON.parse` for the readable structure. Doing it browser-local matters because JWTs in front of you routinely contain email addresses, account identifiers, role claims, signed scope tables, refresh-token references, and customer PII — payloads that have no business going through an arbitrary online service before you have read them. This page **decodes** the token; it does **not verify** it. Decoding is a structural operation — split, Base64URL-decode, JSON-parse — that any holder of the token can do without secrets. Verification is the cryptographic operation that proves the token was actually signed by the issuer it claims to be from, and that needs the issuer's public key (or HMAC secret), the matching algorithm, a JWKS lookup, a clock-skew policy, and ideally a server-side library that reads all of those correctly. A decoded JWT can be expired, forged, tampered with, or replayed; reading the claims is what tells you what the token *says about itself*, not what *the issuer says about the token*. Always treat decoded claims as untrusted input and re-verify on the server before acting on them.

Processing mode

Browser-local

Files are processed by your browser. They never reach our servers.

Questions

JWT Decoder — Free, In Your Browser FAQ

Does this tool verify the JWT signature?

No. The page **decodes** the token — split, Base64URL-decode, JSON-parse — and surfaces the resulting claims; it never verifies the signature, and the UI labels the signature segment as "not verified" exactly so a reader does not misread "decoded successfully" as "valid." Verification is a separate cryptographic operation that needs the issuer's public key (for asymmetric algorithms like RS256, ES256, EdDSA) or the HMAC secret (for symmetric algorithms like HS256), the matching algorithm name, a JWKS lookup against the issuer's well-known endpoint, and a clock-skew policy. None of those are safe to fake in a free-tier browser tool — fetching JWKS would route through the page's origin (not the issuer's), and shipping HMAC verification would need the secret to be pasted into the page, which is exactly what a browser-local decoder is built to avoid. The right place to verify is server-side using a vetted library (`jose` for Node and TypeScript, `PyJWT` for Python, `java-jwt` for Java, `golang-jwt/jwt` for Go) wired against the issuer's real JWKS endpoint with a strict `alg` allowlist.

What's the difference between decoding a JWT and validating it?

Decoding is structural: split the dot-separated segments, run Base64URL on the first two, parse the resulting JSON. Anyone holding the token can do that without secrets — the JWT spec deliberately makes the claims plain-text-readable. Validation is cryptographic: prove that the signature on the third segment was actually produced by the holder of the issuer's signing key, that the algorithm in the header matches what the API expects (which closes the `alg: none` and HS-vs-RS confusion attacks), that the `exp` claim is in the future and `nbf` is in the past against a real clock, that `iss` matches the configured issuer, and that `aud` matches the configured audience. A decoded JWT can be expired, forged, tampered with by a man-in-the-middle, or replayed from an old session; reading the claims tells you what the token *says about itself*, not what *the issuer says about the token*. Always treat decoded claims as untrusted input and re-verify on the server before acting on them.

Why does the JWT have no `=` padding, and how does the decoder handle that?

JWTs use the URL-safe Base64 alphabet (RFC 4648 §5) without padding — the spec strips the trailing `=` because the `=` character would have to be percent-encoded as `%3D` inside a URL, and JWTs are designed to live inside URL fragments, query parameters, and HTTP headers without further escaping. The decoder restores the padding before `atob` runs: a Base64URL string of length `n` is padded to the next multiple of four with `=`, and `-` is mapped back to `+` and `_` is mapped back to `/`. The resulting standard-Base64 string is what `atob` consumes. If you paste a token with the padding already present (some tools emit it for human inspection), the decoder tolerates that too — extra trailing `=` are absorbed by the same logic that adds them.

What does `alg: none` mean, and why does the algorithm chip flag it?

`alg: none` is the JWT-spec encoding of "this token is unsigned." The third segment is empty, the header declares no algorithm, and the token's claims are taken at face value with no signature to check. Historically, `alg: none` was the lever for an entire class of CVE-grade vulnerabilities — verification libraries that read the algorithm from the header (rather than from a strict server-side allowlist) would accept an unsigned token as valid, because the verifier dutifully ran the "no signature" check that the header asked for. The fix on the verification side is a strict allowlist: the server says "we only accept RS256 from this issuer" and refuses any header that says otherwise. The decoder surfaces `alg: none` with an explicit chip so a reader looking at the header is not surprised — and so a verification bug where `alg: none` snuck through can be diagnosed by reading the token rather than reproducing the bug.

How does the local-clock expiry chip work, and how reliable is it?

The chip reads `exp` (or `nbf`) as seconds since the Unix epoch, converts to milliseconds, compares against `Date.now()`, and renders a readable description: `Expired 87 days ago`, `Expires in 12 hours`, `Not yet valid (starts in 4 minutes)`. The reliability is bounded by the browser clock — if the system time on this device is wrong by hours (a freshly-flashed VM, a phone with location services off, an out-of-warranty laptop), the chip will read expiry incorrectly. The page always shows the raw `exp` timestamp alongside the chip so the conclusion can be cross-checked against a known-good clock (a server log, a UTC-clock website, or a watch). Production verification libraries handle this with a configurable `clockTolerance` (typically 30–300 seconds) to absorb clock skew between the issuer and the verifier; the decoder does not apply tolerance because the chip is informational, not authoritative.

Can I decode a JWE (encrypted JWT) or a nested JWT here?

No. JWE (JSON Web Encryption) tokens look superficially similar to JWTs — five segments separated by dots, all Base64URL — but the payload is encrypted, not just signed, and decryption needs the recipient's private key. The decoder will surface the protected header (the first segment) but cannot decode the encrypted payload, and the page surfaces a JWE-detected chip with that explanation rather than rendering meaningless decoded bytes. Nested JWTs (a JWT-inside-a-JWE, or a JWT signed and then re-signed by an envelope) follow the same pattern: each layer is the inner system's responsibility. Use a server-side library (`jose` for Node, `PyJWT` with `cryptography` for Python) wired against the recipient's private key for either case; the browser-local decoder is the wrong tool for encrypted payloads by design.

Does this tool send my JWT to a server?

No. `atob`, `TextDecoder`, and `JSON.parse` are all native browser APIs and run inside this tab. The JWT, the decoded header and payload, and any download all stay on your device. There is no signup, no watermark, no analytics on the token itself. Safe for production tokens that carry email addresses (the `email` claim on OIDC ID tokens), account identifiers (`sub`), role claims (`scope`, `permissions`, `roles`), and signed scope tables — payloads that have no business going through an arbitrary online decoder.

Is there a length limit?

The transform call is capped at 1 MB to keep the parser snappy on lower-RAM devices. Most production JWTs land in the 200 byte to 4 KB range; a token over 100 KB is almost always a sign that the token is actually a JWE with a large encrypted payload, or that a non-token value got pasted by mistake. The cap is generous enough to handle every realistic JWT and small enough that a multi-megabyte paste does not freeze the tab.

Will this tool stay free?

The basic workflow is designed to stay free. Paid upgrades later will focus on bigger limits, batch work, OCR, saved presets, and ad-free use.