Why Your JWT Tokens Keep Failing (And the Free Decoder That Helps You Find Out)
JWT authentication errors are irritating in a specific way. The error message tells you something went wrong, but rarely tells you exactly what. You check the code, the code looks right, you restart the server, and the 401s keep coming.
The actual problem is almost always inside the token itself. The algorithm does not match what the server expects. The issuer field has the staging URL. The expiry was set to 10 minutes and the server clock is 15 seconds ahead. None of these are visible from the error message alone.
The most direct way to find out what a JWT actually contains is to decode it. There are several free tools that do this. One worth knowing about is the JWT Decoder from EvvyTools, which shows the header, payload claims, expiry countdown, and signature verification status in one view. For a full guide on what to look for when debugging, see their post on How to Debug JWT Authentication Errors Using a Token Decoder.
The rest of this post covers why JWT tokens fail and how decoding them helps narrow the cause.
The Token Contains the Answer
A JWT has three parts: a header, a payload, and a signature. The header declares the algorithm used to sign the token. The payload carries claims: who issued the token, who it represents, when it expires, and which service it was intended for. The signature proves the header and payload have not been modified.
When a JWT fails, one of these three sections contains the problem. The header might declare an algorithm the server does not accept. The payload might have an expiry timestamp in the past, an issuer that does not match, or an audience intended for a different service. The signature fails when the wrong key is used or when the token was tampered with.
None of this is visible from the error message the server returns. The server knows the token failed its validation; it does not explain which field caused it. Decoding the token is how you go from "invalid signature" or "jwt expired" to the specific claim or algorithm that is actually wrong.
Photo by Ben Tofan on Unsplash
The Most Common Causes of JWT Failures
Expiry. The exp claim is a Unix timestamp. If the token's expiry time has passed, the server rejects it. This is the most frequent cause of JWT errors in production. Tokens with long expiry windows seem to reduce this, but they create security problems. Short-lived tokens with a refresh mechanism are the better approach. Decoding a failing token shows the expiry timestamp immediately; the EvvyTools decoder converts it to a human-readable date with a countdown.
Algorithm mismatch. The token header declares a signing algorithm. If the server is configured to accept only RS256 but the token was signed with HS256, the signature verification fails even if the token is otherwise valid. This often happens during environment changes or when migrating to a new identity provider. Decoding shows you the algorithm field in the header directly.
Issuer mismatch. The iss claim identifies who issued the token. If your API validates the issuer and the value does not match the expected string, the token fails. Development tokens with staging issuer URLs frequently reach production APIs during deploys, causing a wave of authentication failures. Decoding the token shows the issuer field; comparing it against what your API expects is usually enough to diagnose the problem.
Audience mismatch. The aud claim specifies which service the token was intended for. If an API validates the audience and the token was issued for a different service, it is rejected. This is common in microservice setups where multiple services share an identity provider. Each service should validate that the audience matches its own expected value.
Clock skew. JWT expiry and not-before timestamps are evaluated against the server's clock. If the issuer's clock and the verifier's clock are out of sync by more than a few seconds, a token that looks valid might be rejected as expired or as not yet active. Short clock differences cause intermittent failures that are hard to reproduce.
Choosing a Decoder: What to Look For
There are several JWT decoders available as browser tools and command-line utilities. The useful ones share a few characteristics:
They decode all three sections without requiring a key (reading claims does not require signature verification).
They show the expiry as a human-readable timestamp, not just the raw Unix value.
They support signature verification with your key when you need to confirm authenticity, not just read the claims.
They show the algorithm from the header alongside the other fields.
The EvvyTools JWT Decoder shows all of this. JWT.io is another well-known option that also supports signature verification. Both are reasonable choices; the EvvyTools version additionally shows the expiry countdown live, which is useful when debugging timing-related failures.
For security-sensitive debugging where you cannot paste the token into a browser tool, the jsonwebtoken library in Node.js and PyJWT in Python both support decoding without verification, so you can inspect claims without transmitting the token externally.
Photo by Anna Tarazevich on Pexels
What to Check in the Decoded Token
Work through these in order when debugging a JWT failure:
Check the exp claim. Is it in the past? If yes, the token expired and needs to be refreshed.
Check the alg field in the header. Does it match what your server is configured to accept? HS256 and RS256 are not interchangeable.
Check the iss claim. Does it match the expected issuer string exactly? Including whether it uses http or https, trailing slashes, and any path components.
Check the aud claim. Is it set? Does it match what your API expects? Some tokens do not include an aud claim; if your API requires one, the token fails.
Run signature verification if you have the key. A failed verification with the correct key means the token was signed by a different key or was modified after signing.
If all claims look correct and the signature verifies, check whether the service's clock is synchronized with NTP. The RFC 7519 specification covers how implementations should handle timing claims and the recommended handling of clock skew.
Why the Debug Loop Is Shorter With a Decoder
The alternative to using a decoder is reading the raw base64url-encoded token, decoding each section by hand, and interpreting the JSON. This works but adds several minutes to every debugging cycle.
Having a decoder open during a debugging session reduces the feedback loop from "saw a 401, read the log, guessed at the cause" to "saw a 401, pasted the token, saw the expired claim." The difference is meaningful when you are working through a stack of authentication errors across environments.
For the full systematic approach to JWT debugging, including how to compare tokens from different environments side by side, the EvvyTools guide on debugging JWT errors covers each step in detail. The OWASP Web Security Testing Guide is the authoritative reference for JWT security testing more broadly.
JWT failures are predictable and diagnosable. Decoding the token is the first step that turns "something is wrong with authentication" into "the issuer claim has the staging URL" or "the token expired three minutes ago." Start there, and the fix is usually obvious.




















