On this page

Why You Shouldn’t Paste JWTs Into jwt.io (And What to Use Instead)

A production token lands in your terminal. You need to know who issued it, when it expires, and what claims it carries — fast. So you do what most developers do: open jwt.io, paste the token, and read the decoded payload.

Stop. That JWT is a credential. The site you just pasted it into is operated by a third party, with a history of quietly added analytics and an ongoing rewrite triggered by its own maintainers’ privacy concerns. There is a better answer, and the search query you’re probably here for — “jwt.io alternative privacy” — has a real one.

This guide walks through the actual threat model, what jwt.io’s own team has acknowledged in public, and how to verify a JWT debugger never sends your token anywhere. If you’d rather skip the explanation and decode a token right now in your browser, the JWT Decoder is fully client-side — open it, disconnect from the internet, and watch it still work.

TL;DR: Is jwt.io safe?

Short answer: jwt.io’s homepage warns you itself. Its own warning text reads, in essence, “JWTs are credentials — be careful where you paste them.” That warning is correct, and it applies to jwt.io as much as to any other online tool.

For non-sensitive demo tokens, jwt.io is fine. For anything from a real environment — production, staging, sandbox, partner integration — assume the safer default: decode locally. The rest of this article is the why and the how.

What Actually Happens When You Paste a JWT Into an Online Decoder

A JWT (JSON Web Token) is three Base64URL-encoded segments joined by dots: a header, a payload, and a signature. The header and payload are not encrypted; they are merely encoded. Anyone who holds the token can read its contents — including any service that receives it, even if only to “decode it client-side.”

Several things can happen between you pressing Cmd-V and seeing the decoded claims:

  • Browser history. Many tools historically passed the token in the URL query string, which means it ends up in browser history, parent-tab referrers, and any synced history backend (Chrome Sync, Firefox Sync, Edge profiles).
  • Server logs. Even pages that decode in JavaScript still issue requests for analytics, fonts, and ad scripts. If the token is in the URL, it appears in Referer headers sent to those third parties.
  • Telemetry SDKs. Until September 2019, jwt.io was sending metrics from the page despite advertising client-side-only operation, as documented by engineer Jamie Tanna (Capital One Open Banking). “Client-side” is a claim, not an audit.
  • Future code changes. A site that is client-side today can ship a server-side change tomorrow. As one widely-cited Hacker News commenter put it: “If you make a habit of using these tools one of them will eventually be compromised. Either through a technical hack, financial pressure, purchase by an immoral entity, or a disgruntled employee somewhere along the path.”

None of this requires malice. It only requires that someone, somewhere, between your keyboard and your eyes, has a copy of your token long enough to misuse it.

The Real Threat Model

The first objection is always the same: “But the token expires in 15 minutes — what’s the worst that could happen?” Several things, actually.

Replay within the expiry window. Fifteen minutes is plenty of time for a script monitoring an analytics pipeline, a leaked log file, or a referrer header to grab the token and call your API as you. If the token is a refresh token (which can be valid for days or weeks), the window is much larger.

Claims are a treasure map. A typical payload includes the user ID (sub), tenant ID, role list, audience (aud), issuer (iss), and often custom claims like email, internal flags, or feature entitlements. Even an expired JWT tells an attacker exactly which user to target, which API to hit, and what privilege escalation looks like at your company.

Non-prod tokens leak production-shaped secrets. Open Banking sandbox JWTs and partner-integration tokens often contain certificate fingerprints, key IDs, and audience URLs that map cleanly to production infrastructure. Jamie Tanna described being “burned a number of times” by colleagues pasting non-production JWTs and Open Banking sandbox certificates into jwt.io.

Compliance fallout is independent of impact. If your company is subject to SOC 2, ISO 27001, PCI-DSS, or any HIPAA-adjacent rule, pasting a credential into a third-party service is reportable regardless of whether anything was actually exploited. The audit finding is the incident.

JWT-handling bugs are still shipping. CVE-2026-29000, an authentication bypass in pac4j’s JwtAuthenticator, was disclosed earlier this year. Token-validation bugs are not a 2018 problem; debugging JWTs is exactly when developers reach for online tools, and exactly when leaked tokens are most useful to an attacker watching the same library churn.

What jwt.io’s Own Maintainers Have Said

The strongest argument against pasting tokens into jwt.io comes from jwt.io itself.

In GitHub issue #700, “The Future of JWT.io”, opened on July 19, 2024, an Okta/Auth0 DevRel engineer wrote — verbatim — that “passing a token to the site via the URL query parameters has created concerns about the token’s privacy.” The issue lays out a planned rewrite that switches from URL query parameters to URL fragments, splits decode/encode/verify into separate widgets, and stops auto-loading tokens from the URL on page load.

That is the team that runs jwt.io publicly committing to a redesign because the existing handling is not private enough. As of this writing, parts of the redesign have shipped and parts have not. The exact status is irrelevant to you: the point is that the operator’s own privacy bar is higher than the legacy site, and you have no reliable way to know which version any given visitor is using on any given day.

Separately, PentesterLab’s The State of JWT Libraries on JWT.io (March 28, 2025) found that jwt.io’s curated library list still recommends repositories archived in 2018, libraries last updated in 2015, and at least one implementation that supports HMAC-MD5 (an algorithm that does not exist in the JWT spec). One library on the list extracts the alg value via regex instead of parsing JSON; another signs with “a random string of random length and the payload as the secret.” Louis Nyffenegger, PentesterLab’s founder, summarizes: “Developers may unknowingly choose insecure or abandoned libraries.”

The library catalog is not the same problem as token privacy, but it points in the same direction: the property “popular and convenient” is doing a lot of unjustified work in your trust model.

What “Client-Side JWT Decoder” Actually Means

A truly client-side decoder does three things:

  1. Loads code over HTTPS once, then runs entirely in your browser tab.
  2. Performs decoding in JavaScript using atob() (or a Base64URL helper) and JSON.parse() on the header and payload segments.
  3. Optionally verifies signatures using the Web Crypto API, the same audited primitive that secures your TLS handshakes.

No fetch, no XHR, no navigator.sendBeacon, no analytics ping carrying the token. The token bytes never leave the tab’s memory.

That is not the default. That is a property a tool either has or doesn’t have, and one you can verify yourself in under a minute.

How to Verify a JWT Tool Is Actually Client-Side (60-Second Recipe)

This is the single most useful skill in this article. Run it on every tool that touches credentials, including remove.sh’s.

Method 1: The Airplane-Mode Test

  1. Open the tool in a new tab.
  2. Wait for the page to fully load.
  3. Disable Wi-Fi or enable airplane mode.
  4. Paste a token and decode.

If the decoded header and payload appear, the tool is doing the work locally. If you see a spinner or error, the tool is making a server call — likely with your token in the request body. This is the fastest, most definitive check.

Method 2: The DevTools Network Audit

  1. Open DevTools (F12 or Cmd+Option+I).
  2. Switch to the Network tab.
  3. Filter by Fetch/XHR.
  4. Click Clear (the no-symbol icon), then paste your token.

A client-side decoder will show zero Fetch/XHR requests during the decode. If you see a request, click it and inspect the Payload tab — your token may be in the request body or query string. Even if it isn’t, the tool is reaching the network on every keystroke, which is enough to disqualify it for sensitive work.

Method 3: Read the URL Bar

If the tool puts the token directly into the URL — ?token=eyJhbG... — that token is now in your browser history, your shell history if you copied the URL, the Referer header sent to every analytics script on the next page you visit, and any clipboard manager you use. Tools that use URL fragments (#token=...) are better, because fragments are not sent to servers, but query parameters are an immediate disqualifier.

Try it yourself: Open the JWT Decoder on remove.sh, then run all three checks. The Network panel stays empty after page load, the URL never contains your token, and the page works on a plane. That is a verifiable property, not a marketing claim.

A Privacy-First jwt.io Alternative: Decode in Your Browser

If you’ve followed this far, the recommendation is unsurprising: use a decoder you can verify, then verify it.

remove.sh’s JWT Decoder is built on the same primitives this article describes. It uses atob() for the Base64URL segments, JSON.parse() for the header and payload, and the Web Crypto API for HMAC signature verification (HS256/HS384/HS512). Tokens are never serialized into the URL. There is no analytics SDK reading the input field. The tool reports expiration status by comparing the exp claim to your local clock, and labels registered claims (iss, sub, aud, iat, nbf, jti) so you don’t have to memorize them.

What it does not do, today, is verify RSA or ECDSA signatures (RS256/ES256). For asymmetric signatures, you have two reasonable options, both covered in the next section.

If your token is wrapped in Authorization: Bearer eyJhbG..., paste the whole thing — the decoder strips the Bearer prefix automatically. If you only have the Base64-encoded segments and want to inspect them raw, the Base64 Encoder & Decoder decodes individual segments without trying to interpret them as a JWT, which is useful when you suspect a malformed token.

Verifying Signatures Without Exposing Your Secret

Decoding tells you what a token claims. Verification tells you whether to trust it. The two are not the same operation, and they have different privacy stakes.

HMAC (HS256/HS384/HS512)

HMAC verification requires the shared secret. This is the dangerous one: a leaked secret means anyone can mint valid tokens for your service. Never paste an HMAC secret into an online tool you can’t verify is local.

remove.sh’s JWT Decoder verifies HMAC signatures with the Web Crypto API. The secret stays in your tab’s memory and is never serialized to the network. You can confirm this by running the airplane-mode test with both the token and the secret filled in — the verification still completes.

If you want a stronger guarantee than even an audited browser tab, drop to the local openssl recipe below to recompute the HMAC from the header-payload portion and compare it to the signature segment yourself.

RSA / ECDSA (RS256, RS384, RS512, ES256, ES384, ES512)

Asymmetric verification needs only a public key, which by definition is not secret. This is the easy case for online tools — the data flowing through is not sensitive on its own. The only thing you must protect is the token, which contains the same claims regardless of algorithm.

Until remove.sh ships RSA/ECDSA verification in the decoder, the cleanest local option is step crypto:

step crypto jwt verify \
  --jwks https://your-issuer.example.com/.well-known/jwks.json \
  --iss https://your-issuer.example.com \
  --aud your-audience < token.txt

Or with openssl and a PEM-formatted public key:

# Extract the signed portion (header.payload)
SIGNED=$(cut -d. -f1,2 token.txt)

# Extract the signature, decode Base64URL
cut -d. -f3 token.txt | tr '_-' '/+' | base64 -d > sig.bin

# Verify with openssl
echo -n "$SIGNED" | openssl dgst -sha256 -verify pubkey.pem -signature sig.bin

Both work offline. Both let you verify a token without ever pasting it into a website.

Decoding a JWT Locally With the CLI

For one-off inspections away from any browser, two one-liners cover most cases.

Pretty-print the header:

cut -d. -f1 token.txt | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

Pretty-print the payload:

cut -d. -f2 token.txt | tr '_-' '/+' | base64 -d 2>/dev/null | jq .

The tr '_-' '/+' step converts Base64URL to standard Base64, which is what most decoders expect. The 2>/dev/null swallows the base64: invalid input warning that appears when the segment isn’t padded — jq will still print the JSON correctly.

If you don’t have jq, the JSON Formatter on remove.sh prettifies the output of either command in your browser without sending it anywhere. Pipe to your clipboard with pbcopy (macOS) or xclip -sel clip (Linux), then paste.

How to Safely Share a JWT in a Bug Report

A common reason developers reach for jwt.io is not debugging — it’s pasting a decoded payload into a Slack thread or a ticket so a teammate can see the claims. There’s a safer recipe.

  1. Decode the token locally with one of the methods above.
  2. Copy the decoded JSON only — never the encoded eyJhbG... form. The encoded form is the credential; the decoded JSON is just data.
  3. Redact identifying claims before sharing: replace sub, email, name, custom user IDs, and tenant IDs with placeholder values (sub: "user-redacted").
  4. Drop the signature segment. It cannot be reconstructed from the header and payload without the secret or private key, which means the redacted JSON is no longer a usable credential.
  5. For test fixtures, generate a fresh token signed with a throwaway secret. The shape is what matters for reproducing a bug, not the values.

If a teammate needs to confirm “yes, this is the same token I’m seeing on my end” without either of you sharing the token itself, paste the encoded JWT into the Hash Generator on remove.sh and exchange the SHA-256 fingerprint instead. Same fingerprint means same token; the fingerprint reveals nothing about the claims or signature.

This approach turns a credential exchange into a data exchange, which is what your security policy actually wants.

Frequently Asked Questions

Is jwt.io open source?

The site’s frontend code is published on GitHub, but “open source” doesn’t tell you what’s running on jwt.io today. As Jamie Tanna noted, “we have no idea if the source code… is actually being used” on the live site. Verifiability — a Network tab that stays empty — is a stronger signal than a public repo.

Has jwt.io ever leaked tokens?

There is no public disclosure of a token-leak incident. There is a public acknowledgement from the maintainers (GitHub #700) that the legacy URL-query handling created privacy concerns serious enough to motivate a rewrite. Absence of a known incident is not the same as absence of risk.

Can I self-host jwt.io?

Yes, the source is available, but self-hosting only solves the network-trust problem if you actually audit the build and serve it from infrastructure you control. For most teams, an already-verified client-side tool is less work and less risk than running a fork.

Does decoding a JWT require the secret?

No. Decoding the header and payload only requires Base64URL decoding and JSON parsing — both purely local operations. The secret is only needed to verify the signature, which is a separate step. Any tool that asks for your secret just to display the decoded claims is doing something wrong.

Why does my token show as expired even though I just minted it?

The exp claim is compared against the local clock. Decoders running in a browser use your system clock; CLI tools use the host clock. A skewed clock — common in VMs and CI runners — will make valid tokens look expired and vice versa. Sync your clock (sudo sntp -sS time.apple.com or sudo ntpdate pool.ntp.org) and re-check.

Is it actually safe to paste a token into a tool you’ve verified is client-side?

Yes, with one caveat. A tool that is client-side today may not be tomorrow. The Network-tab check needs to be repeated when you trust a tool with a particularly sensitive token, especially after a long gap. Bookmarking the tool, or using a browser extension that warns on outbound requests from a specific origin, makes this cheaper.

How does this relate to remove.sh’s broader privacy stance?

The same architecture applies to every developer tool on the site. The longer-form argument is in Client-Side Developer Tools: The Privacy-First Approach, which covers JSON formatters, hash generators, Base64 decoders, and the Web Crypto API in detail.

The Bottom Line

jwt.io is a popular convenience that handles a class of data — credentials — for which “popular convenience” is not a sufficient trust model. Its own maintainers have written, in public, that the legacy site has privacy issues serious enough to redesign. The replacement isn’t jwt.io’s redesign or any single competitor; it’s a habit.

The habit is: before any tool sees a token, run the airplane-mode test. If it works offline, it’s local. If it doesn’t, find one that does — or fall back to a CLI one-liner.

The JWT Decoder on remove.sh is built to pass that test. Open it, disable your network, and decode a token. If it still works — and it will — you’ve just verified, with your own eyes, that no third party will ever see that JWT.

That is the answer to “jwt.io alternative privacy.” Not a different brand. A verifiable property.