DEV Community

Mikasa Ackerman
Mikasa Ackerman

Posted on

5 Ways to Debug JWT Tokens (Without Losing Your Mind)

JSON Web Tokens are everywhere — authentication, API authorization, session management. But when something goes wrong with a JWT, debugging can feel like staring at a wall of Base64 gibberish.

Here are 5 practical ways to debug JWT issues quickly and get back to shipping code.

1. Decode the Token First

Before anything else, look at what's actually inside the token. A JWT has three parts separated by dots: header, payload, and signature. The header and payload are just Base64URL-encoded JSON.

You can decode them instantly with a free JWT decoder — paste the token in, and you'll see the parsed header (algorithm, type) and payload (claims like sub, exp, iat) in plain JSON.

This alone solves most debugging sessions. You can immediately check:

  • Is exp in the past? Your token expired.
  • Is iss pointing to the wrong issuer?
  • Are the expected claims actually present?

2. Check the Expiration Timestamp

The most common JWT bug is an expired token. The exp claim is a Unix timestamp — not a human-readable date. When you see "exp": 1709942400, do you know if that's in the past?

Use a timestamp converter to quickly translate Unix timestamps to human-readable dates. Or check it in your JWT decoder output — good tools show the decoded time automatically.

Pro tip: Also check the iat (issued at) and nbf (not before) claims. If your server clock is slightly off from the auth server, tokens might be rejected even if they look valid.

3. Verify the Signature Locally

A JWT can look perfectly valid but have a bad signature. This happens when:

  • The signing key was rotated
  • You're using the wrong key or secret
  • The token was tampered with
  • There's an algorithm mismatch (RS256 vs HS256)

To test this, use a JWT generator to create a token with the same payload but your own secret, then compare. If your application rejects valid-looking tokens, the issue is almost always in the signature verification step.

Check the alg field in the header. If your server expects RS256 but the token says HS256, you've found your bug.

4. Inspect the Network Request

Sometimes the JWT itself is fine — it's the way it's being sent that's broken. Open your browser DevTools and check:

  • Authorization header format: Should be Bearer <token>, not JWT <token> or just the raw token
  • Cookie settings: If using HTTP-only cookies, check SameSite, Secure, and Domain attributes
  • CORS issues: The Authorization header requires proper CORS configuration
  • Token truncation: Some tools or logs truncate long strings — make sure the full token is being sent
// Quick check in browser console
fetch('/api/protected', {
  headers: {
    'Authorization': `Bearer ${localStorage.getItem('token')}`
  }
}).then(r => console.log(r.status, r.headers.get('www-authenticate')));
Enter fullscreen mode Exit fullscreen mode

The WWW-Authenticate response header often contains the specific error (e.g., invalid_token, insufficient_scope).

5. Compare Tokens Side by Side

When a working endpoint suddenly breaks, compare a known-good token with the failing one. Decode both and diff them.

Look for:

  • Missing claims the server expects
  • Different aud (audience) values — common when switching between environments
  • Role or permission changes in custom claims
  • Different issuers — staging vs. production auth servers

A simple side-by-side comparison in a JWT decoder often reveals the difference in seconds.

Bonus: Common JWT Gotchas

  • Clock skew: Auth servers and application servers with different times cause intermittent failures. Most JWT libraries support a clockTolerance option.
  • Token size: JWTs go in HTTP headers. If your token has too many claims, it can exceed header size limits (usually 8KB). Keep payloads lean.
  • Refresh token confusion: Don't send your refresh token where an access token is expected. They look similar but serve different purposes.
  • Base64 vs Base64URL: JWT uses Base64URL encoding (no padding, - and _ instead of + and /). Using standard Base64 decode will produce garbled output.

Wrapping Up

JWT debugging follows a simple pattern: decode → check claims → verify signature → inspect transport. Having the right tools bookmarked saves you from writing throwaway scripts every time.

If you work with JWTs regularly, check out DevToolBox — it has a JWT decoder, JWT generator, and 48 other browser-based dev tools. Everything runs locally, no data leaves your browser.

Happy debugging!

Top comments (0)