A JWT that "doesn't work" usually fails for one of a few reasons. You can diagnose most of them without standing up a server.
Step 1: Decode it
Paste the token into the DevToolbox JWT Debugger to read the header and payload. Decoding is local and safe.
Step 2: Check the claims
- exp — expired? Compare to "now".
- iat — issued-at timestamp.
- nbf — not-valid-before; rejects tokens used too early.
- aud / iss — must match what the API expects.
Step 3: Inspect the algorithm
A mismatch between the token's alg and what the server expects is a common rejection cause. Note the alg: none attack — a server that honors "none" is insecure.
A worked diagnosis
Suppose a call returns 401. Decode the token: you see "exp":1754100000 and "now" is
1754200000. The token expired ten million seconds ago — renew it. If exp is
fine, check aud: a token minted for api-a will be rejected by api-b.
Common failure causes
- Clock skew between client and server near the expiry boundary.
- Wrong
audoriss. - Token truncated when copied.
- Using a refresh token where an access token is required.
- Library expecting a different
algthan the token uses.
Reading the signature (why you can't verify locally)
The signature proves authenticity but requires the secret or public key. You can see its presence and algorithm, but you cannot confirm validity without those keys — which should never be entered into a web tool. Do inspection client-side; do verification server-side.
Access vs refresh tokens
Access tokens are short-lived and authorize requests; refresh tokens are longer-lived and only used to get new access tokens. Confusing the two — sending a refresh token as a bearer — is a frequent 401 cause.
Browser devtools workflow
In the Network tab, find the failing request, copy the Authorization header, paste it
into the debugger, and read the claims. This closed loop finds most issues in under a minute.
- Why does it say expired when it isn't?
- Clock skew or an
nbfin the future. Check both against the current time. - Is decoding safe on a shared site?
- Only if it is client-side. Avoid pasting live tokens into unknown servers.
Using jwt.io vs local decoding
jwt.io is excellent for learning, but it is a server-side site — pasting a live token there shares it. For any real credential, prefer a local decoder like DevToolbox so the token never leaves your machine.
Debugging in a microservices setup
When a request crosses several services, each may validate different claims. Decode the token at the
failing boundary and confirm iss, aud, and exp match what that
service expects. A token valid for service A is routinely rejected by service B.
Logging tokens safely
Never log full tokens in plaintext — they are bearer credentials. If you must trace, log only the
sub and a short prefix, or a hashed reference. This keeps debugging possible without leaking access.
When to escalate to the backend team
If the claims look correct but the API still rejects, the problem is server-side: key rotation,
clock skew on their end, or a mismatched alg in their verifier. At that point, hand over
the decoded (not raw) claims and let them check their verification logic.
Short-lived vs long-lived tokens
Access tokens are deliberately short (minutes) so a leak has limited blast radius; refresh tokens last longer but must be stored carefully. If you keep getting logged out, your access token lifetime may be too short for your usage pattern — that's a backend setting, not a client bug.
When exp is fine but it still 401s
If the expiry checks out, suspect aud/iss mismatch, a rotated signing key, or
a clock skew on the server. Decode and compare each claim against what the API documents it expects; the
mismatch is usually obvious once laid out side by side.
Token size limits
JWTs grow with every custom claim, and some servers reject oversized headers. If a token mysteriously fails, check its length — too many claims (or large arrays in the payload) can breach proxy or server limits.
Debugging in mobile apps
Mobile apps often store tokens in secure storage and attach them automatically. To inspect, capture a request via a proxy or your app's debug logging, then paste the bearer token into a local debugger. Never extract the token to a server-side tool from a production device.
Token troubleshooting, summarized
The fast path: decode, check exp/nbf/aud/iss, confirm
the algorithm matches, and verify the token wasn't truncated. Ninety percent of "my JWT doesn't work"
cases resolve at one of those five checks — all doable client-side before you ever ping a backend.