You paste the token into a decoder, the header and payload look perfect, and the API still answers 401. The token is not lying to you. You are asking it the wrong question.
Decoding reverses Base64URL encoding: it needs no key and proves nothing. Verifying recomputes the signature with the signing secret or public key, then checks the claims against what the API expects. The decoder tells you what the token says ; only the verifier tells you whether the API should believe it.
1. The expiry bug that looks like a valid token
JWT timestamps ( exp , nbf , iat ) are NumericDate values: whole seconds since 1 January 1970 UTC (RFC 7519). JavaScript's Date.now() returns milliseconds, so an issuer storing it directly as exp gets the lifetime wrong by a factor of a thousand — while the payload still looks normal.
The test is trivial: a 13-digit exp is milliseconds — divide by 1000. The same bug in nbf or iat makes a token look "not yet valid" or impossibly old.
2.…