2026-06-29 · 6 min read · Rahul Chitturi
- jwt
- auth
- debugging
A JWT can decode cleanly and still receive a 401 from an API. Decoding proves the token is readable, not that the server trusts it, received it, or evaluated the same claims you are looking at in a browser.
When a token works in one environment and fails in another, debug the whole auth boundary: request headers, proxies, issuer configuration, audience, time claims, and the verifier policy.
Confirm the server receives the token
Start at the HTTP request before changing signing keys. The Authorization header should reach the application exactly once as Bearer followed by the compact token. API gateways, CORS preflight paths, reverse proxies, and framework middleware can drop or rename headers.
- Check for a missing Bearer prefix or extra quotes
- Confirm the request is hitting the protected route, not a fallback
- Compare browser, curl, and server logs with secrets redacted
- Look for proxies that strip Authorization on redirects
Read claims as verifier inputs
Audience, issuer, subject, expiration, not-before, and scopes are inputs to the verifier. A token issued for a frontend client may decode correctly but fail an API that expects a different audience. A staging issuer against production middleware produces the same symptom.
Do not fix this by relaxing claim checks globally. Identify which claim differs from the API contract, then update the issuer configuration or the caller that requested the token.
Separate time bugs from trust bugs
Expired, not-yet-valid, and issued-at errors can be masked as generic 401 responses. Convert exp and nbf claims from Unix seconds, compare them with server time, and check clock skew policy before rotating secrets.
- JWT numeric dates are seconds, not JavaScript milliseconds
- Serverless and container clocks should use provider time sync
- Skew should be short and documented
- Hard-coded test tokens may have expired months ago
A Flashman workflow
Decode a non-production JWT locally, convert time claims with the timestamp tool, format any JSON auth error, and diff working versus failing decoded claims. This reveals whether the problem is transport, claim mismatch, expiry, or environment drift.
Flashman keeps the debugging loop in your browser, but tokens still deserve caution. Prefer synthetic or staging tokens, redact bearer strings from tickets, and never authorize production requests from decoded content alone.