You're probably fine. But let me explain why.
Every few months a thread pops up on Reddit or Slack: "Is it safe to paste my JWT into jwt.io?"
The honest answer is: it depends on the token, and most devs already know the safe answer but want confirmation.
Here's the thing about JWTs — they're not encrypted by default. They're just base64-encoded. Decoding the header and payload reveals the claims (user ID, roles, expiry, etc.) but not the signature secret. So pasting the payload into a decoder doesn't inherently expose anything a motivated attacker couldn't already get from intercepting the token in transit.
But there are two real concerns worth thinking about:
Access tokens with sensitive claims — Some JWTs contain internal user IDs, email addresses, org IDs, or permission scopes. Pasting those into a third-party site means you've sent that data to someone else's server.
Trust surface — Even if jwt.io says it decodes client-side, do you know that for sure? Do you trust every CDN it loads from? Do you trust your browser extensions not to capture the input?
Most of the time, for a random dev token in a local dev environment: you're fine. But if you're debugging a production token with real user data? It's worth being a little more intentional.
Option 1: Decode it with atob() yourself
You don't need a tool at all. Open DevTools, paste this:
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
const [header, payload] = token.split('.').slice(0, 2).map(
part => JSON.parse(atob(part.replace(/-/g, '+').replace(/_/g, '/')))
);
console.log('Header:', header);
console.log('Payload:', payload);
Done. No network request. No third-party site. Zero trust required.
This is genuinely the cleanest option for one-off decoding when you're already in the browser or Node.
Option 2: Use a client-side decoder tool
Sometimes you want a nicer UI — syntax highlighting, expiry countdown, a clean view of all claims. For that, AnyTools.io has a JWT decoder that:
- Runs entirely in your browser (no token sent to any server)
- Requires no login, no account, no cookies
- Shows header, payload, and expiry at a glance
I use it when I want something human-readable during a debugging session. It's part of a broader toolkit (JSON formatter, cron builder, color palette, etc.) that I keep bookmarked for the kind of small-but-annoying tasks that eat five minutes if you don't have the right tool.
(Full disclosure: I work as a marketing advocate for AnyTools.io. But I also actually use it, which is why I took the job.)
When to be more careful
If you're dealing with long-lived tokens, internal admin JWTs, or tokens that contain sensitive PII — consider decoding them on an air-gapped machine or in a private VM. The browser DevTools approach (atob()) is safest for those cases.
For everything else in a normal dev workflow, be intentional, not paranoid.
Quick reference
| Scenario | Best approach |
|---|---|
| Quick debugging, dev environment |
atob() in DevTools |
| Want a nice UI, no sensitive data | Client-side decoder tool (e.g. anytools.io) |
| Production tokens with real PII | DevTools only, or dedicated local tool |
| Batch decoding / automation | Write a small script (jwt.decode() in Node) |
TL;DR
JWT decoders don't need your secret key to read the payload — that part is just base64. The real risk is sending sensitive claim data to a third party's server. Mitigate that by using atob() in DevTools or a client-side-only tool. Don't panic, just be intentional.
If you've got a smarter approach or a tool I'm missing, drop it in the comments.
Top comments (1)
Quick note — I'm affiliated with AnyTools.io, so I'm biased on that mention. The
atob()approach in DevTools is genuinely the zero-trust option and I'd use it regardless.