Herramientas · Texto · Decodificador de JWT · Guías · Debugging a 401 by Reading What Your JWT's Claims Actually Say

Debugging a 401 by Reading What Your JWT's Claims Actually Say

4 min de lectura

An API call that worked five minutes ago suddenly returns 401, and the token being sent looks fine at a glance - same length, same three dot-separated segments, nothing obviously broken. The fastest way to find out what actually changed is to decode it and read the claims, not guess. Here's the actual order worth checking, from most to least common cause.

Four checks, in order, from a decoded token to the actual reason a request was rejected A decoded JWT branches into four checks in order: expiry and not-before timestamps, audience and issuer claims, role and scope claims, and the header's signing algorithm - with a final note that signature verification itself cannot be checked this way. Decoded token 1. exp / nbf most common cause 2. aud / iss wrong destination 3. role / scope 403, not 401 4. alg key rotation
Cheapest and most common check first. The signature itself is the one thing none of these four can confirm.

Check the expiry first - it's the most common cause by far

Paste the token into JWT decoder and look at the Timestamps panel before anything else. A token marked (expired) next to its exp date explains a 401 completely and needs no further investigation - the client just needs a fresh token, usually through whatever refresh flow the app already has. Less commonly, a token marked (not yet valid) against its nbf claim means the request landed before the token's own activation time, which usually points at a clock skew between systems rather than anything wrong with the token itself.

If it's not expired, check who the token says it's for

Look at the decoded payload for aud (audience) and iss (issuer) - two claims that exist specifically so a server can reject a token that's technically valid but wasn't meant for it. A token issued for one API and presented to a different one, or issued by an identity provider a service doesn't trust, decodes perfectly and shows a currently-valid expiry while still failing, because the server checks these values against its own configuration before it even reaches the signature. If aud holds a value that doesn't match the service you're calling, that's the actual answer - not a signature problem, not an expiry problem, just a token that was never meant for this destination.

If it's a 403, not a 401, check role and scope claims

A 401 means the server doesn't recognize the request as authenticated at all. A 403 means it does, and is refusing anyway - almost always a permissions problem, visible in the payload as a role, scope, or application-specific claim the token simply doesn't carry the value it needs. A token that decodes cleanly, isn't expired, and has the right audience but is still rejected with a 403 is usually telling you the account genuinely doesn't have the access being requested - decoding the payload confirms that directly instead of leaving it as a guess.

Check the header if a signing algorithm mismatch is suspected

The Header block usually decodes to something short - typically just alg and typ. If a service was recently reconfigured to sign with a different algorithm or a different key, an old token issued under the previous configuration decodes fine and reads as unexpired, but fails signature verification server-side anyway. Comparing the header's alg value against what the service is currently configured to expect is a fast way to confirm or rule this out before chasing anything else.

A mismatch between environments looks the same as a wrong audience

A specific, common version of the audience check above: a token minted under a staging environment's signing configuration ends up in a request against production, or the other way around, usually by copying a token from the wrong browser tab or the wrong .env file. It decodes perfectly, isn't expired, and can even carry a plausible-looking aud value - just not the one the environment you're actually calling expects. If your team runs separate staging and production identity providers or signing keys, decoding the token and checking iss against which environment actually issued it is usually faster than re-checking credentials or re-running the login flow from scratch - especially when the bug report says "it worked in staging," which is itself a strong hint that the token, not the code, is what's different.

What decoding genuinely cannot tell you

Every check above reads claims the token is honestly carrying - if the token were tampered with or forged, none of it would be trustworthy on its own. Confirming a token is genuinely valid means verifying its signature against the real signing key, and that check has to happen server-side, with the actual secret or private key material, never by pasting a token into any page on the public internet, including this one. If everything above checks out - unexpired, right audience, right role - and the request still fails, the remaining explanations live in server logs or the signing configuration itself, not in anything visible from decoding the token alone.

For reading the expiry specifically, see the how-to guide. For why a token can be entirely legitimate and still be rejected by design, see the background. Or decode a token now.

Probar Decodificador de JWT gratis Decodificador de JWT

Herramientas relacionadas