JSON Web Tokens (JWTs) are everywhere in modern authentication. They carry user identity, permissions, session state, and expiration data in a compact, URL-safe format. Naturally, developers need to inspect them during debugging — checking whether a token has the right claims, whether it has expired, or whether the issuer and audience fields match. The problem is how most people do it: by carelessly pasting them into random online debuggers.
In this guide, we'll explore why pasting JWTs into third-party servers is a critical security vulnerability, how JWTs are actually constructed, and how you can decode and debug them safely using client-side tools or terminal commands.
The Problem: Server-Side Token Debuggers
The most popular JWT debugging tools often work by sending your token to a backend server that decodes and returns the parts. That round trip creates several severe risks for your application's security posture:
- Server-Side Logging: Even well-intentioned services may log incoming HTTP requests for debugging or analytics. That log now contains a valid token that can impersonate a user until it expires.
- Network Interception: If HTTPS terminates at a CDN or load balancer before reaching the actual decoding server, the token travels in plaintext through internal infrastructure.
- Third-Party Retention: Some services retain input data for machine learning, abuse detection, or analytics. Your production tokens could become permanent training data.
- Token Replay Attacks: If a token is intercepted or logged before its expiration time, malicious actors can use it to authenticate as the original user on any system that trusts the issuer.
What's Actually in a JWT?
A JWT is not encrypted; it is merely Base64url-encoded. It consists of three segments joined by dots (.):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIiwiZXhwIjoxNzE2NjcyMDAwfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c 1. The Header
The first part (eyJhbGci...) tells you the algorithm and token type. Decoded, it looks like:
{
"alg": "HS256",
"typ": "JWT"
} 2. The Payload
The middle part (eyJzdWIi...) contains the claims—data like user ID, roles, name, and expiration. Decoded, it looks like:
{
"sub": "1234567890",
"name": "Jane Doe",
"exp": 1716672000
} Security Warning: Because the payload is only Base64-encoded and not encrypted, anyone who intercepts the token can read the claims. Never put sensitive data like passwords or social security numbers in a JWT payload.
3. The Signature
The final part ensures data integrity. It is generated using the header, the payload, and a secret key (for HMAC) or a private key (for RSA). If anyone modifies the payload, the signature will no longer match (you can test this yourself with a JWT Signature Verifier), and the receiving server will reject it.
How to Safely Decode JWTs Locally
Using the Terminal
You can easily decode a JWT payload directly in your terminal without any external tools. Here is a handy Bash one-liner using jq and base64:
# Set your token variable
TOKEN="eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRG9lIn0.signature"
# Decode the Payload
echo $TOKEN | cut -d'.' -f2 | base64 --decode | jq Expected Output:
{
"sub": "1234567890",
"name": "Jane Doe"
} Note: If you get a "invalid input" error from base64, it's because JWTs use Base64URL encoding, which omits padding characters (=). You may need to append padding depending on your OS.
Using a Client-Side Browser Tool
A client-side JWT debugger runs the decode step entirely in your browser using JavaScript. The token never leaves your device, which means:
- No server logs contain your token.
- No network request carries sensitive claims.
- You can inspect production tokens safely during incident response.
- You can verify the Network tab yourself — zero outbound requests.
This matters most when you are debugging authentication failures in production, inspecting OAuth tokens from third-party providers, or reviewing tokens that contain PII (names, emails, phone numbers) in the payload.
What to Check When Debugging a JWT
When you decode a token to debug an authentication failure, look for these common pitfalls:
- Expiration (
exp): Is the token still valid? Compare theexpclaim (Unix timestamp) against the current time (a JWT Expiry Checker can do this easily). Note that servers might have clock skew. - Issuer (
iss) and Audience (aud): Do they match what your API expects? Mismatches are a common cause of silent 401s in microservices. - Algorithm (
alg): Is it the expected algorithm? Watch for the infamousalg: nonevulnerability or unexpected symmetric algorithms (HS256) in tokens that should be asymmetric (RS256). - Custom claims: Are the roles, permissions, or tenant IDs correct? Stale claims from cached tokens can cause authorization drift.
Common JWT Error Messages (Troubleshooting)
1. TokenExpiredError
Symptom: The server rejects the token with TokenExpiredError: jwt expired.
Fix: The exp claim timestamp has passed. The user needs to authenticate again or use a Refresh Token to obtain a new JWT.
2. JsonWebTokenError: invalid signature
Symptom: The server rejects the token because the signature check failed.
Fix: Either the payload/header was modified, or you are using the wrong secret/public key to verify the token. Double-check your environment variables.
3. JsonWebTokenError: jwt malformed
Symptom: The string provided is not a valid JWT.
Fix: Ensure the token has exactly three parts separated by two dots. Ensure that you aren't accidentally including the Bearer prefix in your verification function.
Comparison: Debugging Methods
| Debugging Method | Security Risk | Ease of Use | Best For |
|---|---|---|---|
| Online Server-Side Tools | High (Tokens are transmitted) | Very Easy | Never (especially not for production tokens) |
| Terminal / CLI Scripts | None (100% Local) | Moderate | Quick checks, CI/CD pipelines |
| Client-Side Web Tools | None (Runs in browser) | Very Easy | Visual inspection, debugging production errors |
Try It Now
The JWT Debugger on ZeroData Tools decodes tokens entirely in your browser. Paste a JWT, see the header, payload, and expiration status instantly — with zero data uploaded.
If you need to generate test tokens for development, the JWT Generator lets you create signed JWTs with custom claims using HMAC keys — also fully client-side.
Advanced Troubleshooting & Edge Cases
Debugging JSON Web Tokens often goes beyond just checking the signature and expiration time. In complex, distributed microservice architectures, token validation can fail for incredibly subtle reasons. Let's examine some advanced edge cases and how to troubleshoot them without exposing your tokens.
1. The "alg: none" Vulnerability
Historically, the JWT specification allowed for an algorithm type of none. This was intended for situations where the token had already been verified by a secure transport layer (like mutual TLS). However, attackers quickly realized that if they stripped the signature from a JWT and changed the header to "alg": "none", poorly configured backend libraries would accept the token as valid because they assumed no signature was required.
How to debug: When inspecting a JWT client-side, always verify that the alg header explicitly matches the algorithm your server expects (e.g., RS256 or HS256). Never configure your backend JWT validation library to automatically trust whatever algorithm is specified in the token header. Hardcode the expected algorithm in your server configuration.
2. Key Rotation and Key ID (kid) Mismatches
In enterprise environments, signing keys are frequently rotated for security purposes. When a key is rotated, tokens signed with the old key might still be in circulation. If your server only knows about the new key, it will reject all existing, perfectly valid sessions, logging users out unexpectedly.
To solve this, JWTs often include a Key ID (kid) in the header. This tells the validating server exactly which public key (out of a possible set) should be used to verify the signature.
How to debug: If you are seeing sudden, widespread invalid signature errors after a deployment, decode the token header locally and check the kid claim. Then, fetch your server's JWKS (JSON Web Key Set) endpoint (typically located at /.well-known/jwks.json) and ensure that a key with that exact kid is still present and valid. If the key has been purged from the JWKS prematurely, the signatures cannot be verified.
Common Developer Misconceptions
Misconception: JWTs automatically invalidate when a user logs out
This is arguably the most dangerous misconception regarding JWTs. Because JWTs are stateless by design, the server does not keep track of them in a database. When a user clicks "Log Out", the frontend deletes the token from local storage or cookies. However, the token itself remains mathematically valid until its exp (expiration) timestamp is reached.
If an attacker intercepted that token before the user logged out, the attacker can continue to use it. The server has no way of knowing the user intended to end the session.
The Solution: To handle immediate revocation, you must implement a "Denylist" (or blocklist). When a user logs out or changes their password, the token's unique ID (the jti claim) or the user's ID combined with an issuance timestamp (the iat claim) is added to a fast, in-memory store like Redis. During token validation, your server must quickly check this Denylist before trusting the signature.
Frequently Asked Questions
- Is it safe to paste a JWT into an online debugger?
- It depends on the tool. If the debugger sends your token to a server for decoding, that token could be logged, cached, or intercepted. Client-side-only tools that decode in the browser eliminate this risk.
- What information does a JWT contain?
- A JWT typically contains a header (algorithm and token type), a payload (claims like user ID, roles, email, and expiration), and a signature. The payload is only Base64-encoded, not encrypted, so anyone with the token can read the claims.
- Can I verify a JWT signature in the browser?
- You can decode and inspect the header and payload in any browser. Signature verification requires the signing key, which a client-side tool can accept as local input without transmitting it.
- Why does my JWT have no signature?
- If a JWT ends with a dot and has no third segment, it is using the
nonealgorithm. This is extremely insecure and should be rejected by any production backend.