К блогу
tutorial 2026-06-14

A Privacy-Safe JWT Debugger That Never Sends Your Token (2026)

A Privacy-Safe JWT Debugger That Never Sends Your Token

You're debugging an OAuth flow at 11 PM, you grab a JWT out of your Network tab to confirm a claim is set correctly, you paste it into the first JWT decoder Google shows you, and a moment later you remember that you just gave a third-party website a working access token signed by your production identity provider — fully capable of logging into your customer's account, calling your internal API, and reading whatever the token's scopes permit.

Almost every popular JWT decoder on the public web — jwt.io, jwtdecoder.net, the dozens of clones — posts your pasted token to a server before showing you what's inside. Their privacy pages say "we don't store it" which may even be true, but the token still crossed the open internet, hit a third-party HTTPS endpoint, and lived in their HTTP access log for some retention period. For production tokens, that's a security incident worth disclosing.

This guide explains how JWTs actually work, what "decoding" a JWT really does, and how to debug them safely using Ai2Done's JWT Decoder which runs 100% in your browser tab.

TL;DR

  • A JWT is just three base64 strings separated by dots — header, payload, and signature.
  • "Decoding" a JWT is just base64-decoding the first two parts. It is not signature verification.
  • Most online JWT decoders post your token to their server. Even if they say they don't log it, the token still left your machine and entered an access log somewhere.
  • The Ai2Done JWT Decoder runs entirely in your browser — your token never leaves your tab.
  • For production tokens with real privileges, this is the only safe pattern. Treat every paste of a production JWT into a third-party tool as if you had handed someone the password.

Why this is harder than it looks

A JWT (JSON Web Token, RFC 7519) is structurally trivial:

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE3NTAwMDAwMDB9.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

That's three parts separated by .:

  1. Header (eyJhbGciOiJIUzI1NiJ9) — base64-encoded JSON {"alg":"HS256"} describing the signing algorithm.
  2. Payload (eyJzdWIi...) — base64-encoded JSON with the actual claims (sub, exp, custom fields).
  3. Signature (dBjftJeZ...) — HMAC or RSA/ECDSA over header.payload, used to verify integrity.

"Decoding" a JWT means base64-decoding parts 1 and 2 to read the JSON inside. This requires no secret key, no network call, no server. It's a 5-line JavaScript function. Yet 99% of online JWT decoders insist on round-tripping your token through their backend, which means the token enters:

  • Their server's HTTPS termination log
  • Their reverse proxy access log
  • Possibly their application log (depending on how they implement it)
  • Their CDN's edge logs (Cloudflare, Fastly, etc.)
  • The corresponding logs of any partner ad networks, error tracking services, or analytics providers loaded on the page

Even with the best intentions on the operator's side, your production token is now persisted in 4-6 different log files across 2-3 different companies, for retention periods ranging from 7 days to "forever." A determined attacker compromising any of those targets has a working bearer token to your customer's resources.

This isn't paranoia. It's standard threat modelling for anyone handling production credentials.

Method 1: Ai2Done JWT Decoder (browser-side, the right way)

The Ai2Done JWT Decoder is 200 lines of vanilla JavaScript running in your browser tab. Paste a token, see what's inside, close the tab, and the token is gone forever. The flow:

  1. Open /tools/jwt_decoder in any browser.
  2. Paste your JWT into the input area.
  3. The decoded header and payload appear instantly in syntax-highlighted JSON panels.
  4. Optionally, paste your signing secret (HS256) or public key (RS256) to verify the signature locally. The verification also runs entirely client-side.

What you get out of the box:

  • Header decoding with algorithm and key ID
  • Payload decoding with all custom claims and standard claims (iss, sub, aud, exp, nbf, iat, jti)
  • Human-readable timestampsexp: 1750000000 is also shown as "in 47 minutes" so you can immediately see if the token is expired or near-expired
  • Claim validation warnings — if exp is in the past, the field is highlighted red; if aud doesn't match a value you expect, you can paste the expected aud for instant comparison
  • Signature verification — paste your HS256 secret, RS256 public key (PEM), or JWKS URI, and the tool tells you whether the signature is valid. The verification uses Web Crypto API entirely in-browser.

The whole bundle is ~40 KB. It loads in <1 second on any device, works offline once cached, and has zero network calls after the initial page load.

Method 2: A 5-line script in your terminal

For developers who already work in a terminal, the simplest possible JWT decoder is one line of bash:

echo "YOUR_JWT_HERE" | cut -d. -f2 | base64 -d 2>/dev/null | jq .

This:

  1. Pipes the JWT into cut to grab the second part (payload)
  2. base64-decodes it (2>/dev/null silences padding warnings)
  3. Pretty-prints with jq

For the header, replace f2 with f1. For signature verification, use a real library — python -c "import jwt; print(jwt.decode(...))" or the equivalent in your language. Don't try to implement HMAC-SHA256 verification in bash.

This is the right answer for "I'm already SSHed into a server and I need to inspect a token right now." It's overkill if you're already in a browser.

Method 3: A native macOS / Windows app

For folks who hate browser tools, there are several offline-only JWT decoders:

  • macOS: JWT Inspector (free, from the App Store)
  • Windows: JWT Decoder by IDESoft (free, from MS Store)
  • Cross-platform: VS Code extension "JWT Debugger" (works inside your editor)

All run entirely on your device with no network requests. Pick whichever fits your workflow.

How we built it (technical deep-dive)

The Ai2Done JWT Decoder is deliberately minimal:

  • No frameworks — vanilla JS, ~200 lines, ~40 KB gzipped. Loads instantly even on a 3G connection.
  • No analytics, no telemetry — we don't even know how many people use it, by design. Page views are recorded on the route level (/tools/jwt_decoder got X visits) but never with any token-derived information.
  • base64url-safe by default — JWTs use the URL-safe variant of base64 (-_ instead of +/); we handle both.
  • Web Crypto for signature verificationcrypto.subtle.verify() natively supports HMAC-SHA256/384/512 and RS256/384/512. ES256/384 (ECDSA) also works on modern browsers. PS256 (RSA-PSS) requires a small JS shim because some older browsers lack it.
  • JWKS support — paste a JWKS URI (e.g. https://accounts.google.com/.well-known/jwks.json), the tool fetches the JWK set, finds the key matching the token's kid, and verifies the signature. The fetch happens from your browser directly to the IdP, never through us.

The one design decision worth flagging: we deliberately do not offer "sign a JWT" functionality. The tool is read-only. Signing requires a secret key, and we don't want users in the habit of pasting their HMAC secret or RSA private key into web apps. Sign your tokens with a real library in your application code; use this tool to verify what came out.

FAQ

Q: Is "decoding" a JWT a security risk by itself? A: No — base64-decoding the header and payload reveals only what the issuer chose to put in there. The JWT spec is explicit: payload is base64-encoded, not encrypted. Anyone who has the token can read what's inside. The real risk is letting the token itself leak, because the token is a bearer credential — whoever has it can use it.

Q: My token's exp is in the past but the API still accepts it. Why? A: A few possible reasons: (1) the server is checking a different timestamp claim like nbf or iat and ignoring exp, (2) the server has clock skew and accepts tokens up to N minutes past expiry, (3) the token type is actually a refresh token that doesn't expire as fast as access tokens, or (4) the API genuinely has a bug. Check your IdP's validation policy.

Q: How do I verify the signature without the IdP's public key? A: You can't, by design — that's the whole point. The signature proves the token was issued by someone who knew the signing secret. If you don't have the public key (for RS256) or the shared secret (for HS256), you cannot verify integrity. The decoder will tell you "unverified — provide a key to verify." This is correct behavior.

Q: Why does the tool show some claims as standard and others as custom? A: RFC 7519 reserves a set of standard claim names: iss, sub, aud, exp, nbf, iat, jti. Anything else is custom (e.g. name, email, roles, application-specific claims). The decoder highlights standard claims with their RFC-defined semantics and treats everything else as freely-shaped JSON.

Q: Can I decode an encrypted JWE token? A: Not without the decryption key. JWE (JSON Web Encryption, RFC 7516) is genuinely encrypted, so its payload is opaque without the key. The tool detects JWE vs JWS based on the header's enc claim and tells you it can't decode encrypted tokens without a key (and we don't offer key paste because that's a sensitive operation that should happen in your own code, not a web tool).

Q: Does the tool work offline? A: Yes. After the first visit, the tool is in your browser's HTTP cache and works without internet access — useful when debugging tokens in airgapped environments or when your network is down.

Try it now

Debug your JWT without giving it to a stranger:

Open the JWT Decoder →

Paste, see, close. Your token never leaves your tab.

Related reading


Last updated 2026-06-14. The JWT Decoder runs 100% in your browser tab — your tokens never leave your device. We have no server log, no analytics on token content, and no way to retrieve a decoded token after you close the tab.