Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool
Debugging JSON Web Tokens: Common Pitfalls and Solutions
Start With the Shape of the Token
Most JWT debugging sessions go faster once you stop treating the token as a black box. The current JWT spec in RFC 7519 defines JWTs as URL-safe dot-separated segments, and the current best-practice guidance in RFC 8725 recommends explicit algorithm allow-lists, strict claim validation, and separate rules for different token types.
In practice, most failures come from six places: malformed compact serialization, wrong key or algorithm, time-claim mistakes, issuer or audience mismatches, stale JWKS key rotation, or a transport bug that changes the token before verification.
First-Pass Checklist
Count the segments first. A signed JWT or JWS normally has 3 parts. An encrypted JWE has 5 parts. If you send a 5-part token into a 3-part verifier, the error often looks unrelated.
Decode the header and payload locally before you verify anything. Record
alg,kid,typ,iss,aud,sub,exp,nbf, andiat.Verify with an explicit allow-list of algorithms and the exact expected issuer and audience. Do not let the incoming token choose its own validation rules.
Compare the raw token, the selected key, and the system clock. A stale
kidlookup or a time skew of even a minute can be enough to break validation.
Common JWT Pitfalls and Fixes
Pitfall 1: Malformed Token, Base64url Errors, or JWE vs JWS Confusion
RFC 7519 defines JWTs as period-separated base64url segments. That matters because base64url uses - and _ instead of + and /, and padding may be omitted. A parser that expects regular base64, or code that edits the token by hand, can produce misleading "invalid token" or "invalid signature" failures.
Common causes: copying the entire Authorization header instead of the raw token, trimming or wrapping lines, using a standard base64 decoder, or passing a 5-part encrypted JWE into a verifier that expects a 3-part signed JWS.
What to do:
- Split on periods before anything else. Expect 3 parts for JWS and 5 parts for JWE.
- Use a base64url-aware decoder, not a plain base64 helper copied from unrelated code.
- Strip the leading
Bearerprefix exactly once and then compare the raw token bytes. - Never edit header or payload JSON and then reuse the old signature. Any change invalidates it.
Quick shape check:
const raw = authHeader.replace(/^Bearer\s+/i, "").trim();
const parts = raw.split(".");
if (parts.length === 3) {
// Signed JWT / JWS
} else if (parts.length === 5) {
// Encrypted JWT / JWE
} else {
throw new Error("Malformed JWT compact serialization");
}Pitfall 2: Invalid Signature Because the Key or Algorithm Does Not Match
This is still the most common production failure. The token may be perfectly well-formed, but the verifier is using the wrong secret, the wrong public key, or the wrong algorithm family entirely.
Common causes: verifying an RS256 token with an HMAC secret, using the wrong environment key, stale PEM files with stray newlines, or letting the incoming alg header influence verification instead of pinning allowed algorithms server-side.
What to do:
- Log the decoded header first and record the incoming
algandkid. - Configure the verifier with an explicit allow-list such as only
RS256. - Confirm that the key type matches the algorithm family: HMAC secret for HS*, public key for RS* or ES*.
- Check key material for whitespace, newline, or copy-paste corruption before assuming the token is bad.
- Reject
alg: "none"unless you intentionally support unsecured JWTs inside another trusted cryptographic envelope.
Pitfall 3: Expired, Not-Yet-Valid, or Milliseconds-vs-Seconds Time Claims
RFC 7519 uses NumericDate values for exp, nbf, and iat. Those are seconds since the Unix epoch, not JavaScript milliseconds. That single mismatch causes a large share of "token expired" and "token not active" bugs.
Common causes: storing Date.now() directly in the token, comparing seconds to milliseconds during validation, or having issuer and verifier clocks drift apart.
What to do:
- Convert application time to seconds with
Math.floor(Date.now() / 1000). - Inspect whether
expornbflooks 1000x too large or too small. - Allow a small clock-skew leeway only when you need it, usually a minute or two.
- Synchronize issuer and verifier clocks with NTP before debugging anything else.
Sanity check for NumericDate handling:
const now = Math.floor(Date.now() / 1000);
if (payload.nbf && now < payload.nbf) {
throw new Error("JWT is not valid yet");
}
if (payload.exp && now >= payload.exp) {
throw new Error("JWT has expired");
}Pitfall 4: Claim Validation Bugs in `iss`, `aud`, `sub`, or Token Type
A token can be correctly signed and still be invalid for your application. The most common example is sending the wrong token type to the wrong service, such as an ID token where an API expects an access token.
Common causes: wrong issuer URL for the environment, aud treated as a string when it is actually an array, tenant or realm mismatch, or one validator being reused for multiple JWT profiles that should be distinct.
What to do:
- Log the expected and actual values for
iss,aud, andsub. - Handle both the single-string and array forms of
audcorrectly. - Keep separate validation rules for access tokens, ID tokens, and other JWT-based artifacts.
- Use distinct audiences or a checked
typvalue to reduce cross-token confusion. - Do not trust authorization claims until signature and claim validation have both passed.
Pitfall 5: `kid` Lookup, JWKS Caching, and Key Rotation Problems
If your application verifies JWTs against a JWKS endpoint, signature bugs often appear only after a key rotation or only in some regions. The token is valid, but the verifier picked the wrong cached key or never fetched the new one.
Common causes: unknown kid, stale cache entries, duplicate or recycled kid values, or rotating signing keys before older tokens have naturally expired.
What to do:
- Log which
kidwas requested and which key was actually selected. - Refresh the JWKS cache on an unknown
kidbefore declaring the token invalid. - Keep previous verification keys available until all tokens signed with them have aged out.
- Make sure your rotation process never reuses a
kidfor different key material. - Treat remote key lookup as a controlled integration. RFC 8725 warns against blindly trusting incoming key URLs such as
jkuorx5u.
Pitfall 6: Transport Bugs That Change the Token Before Verification
Sometimes the JWT is correct and your verification code is correct, but the token is altered between the client, a proxy, and the application. This is why JWT bugs often reproduce in one environment and not another.
Common causes: missing Bearer prefix handling, double URL encoding, proxies that drop the Authorization header, multiline environment variables, or logging only the normalized token rather than the raw received value.
What to do:
- Inspect the actual HTTP request in browser devtools,
curl, or Postman. - Compare the raw token string on the sender and receiver, including length.
- Check reverse proxies, API gateways, and middleware for header forwarding rules.
- Redact safely in production logs, but still keep enough data to distinguish truncation from claim failures.
A Practical JWT Debugging Workflow
- Capture the raw token before any parsing or normalization.
- Count the segments to distinguish malformed input, JWS, and JWE.
- Decode the visible parts locally and pretty-print the JSON for the header and payload.
- Record
alg,kid,typ,iss,aud,exp,nbf, andiat. - Re-run verification with an explicit algorithm allow-list and exact expected issuer and audience.
- If you use JWKS, refresh the cache and test both current and previous verification keys.
- Only after all of that should you start suspecting a library bug.
Conclusion
JWT debugging is usually a comparison exercise, not a guessing exercise. Compare the raw token, its shape, the decoded claims, the selected key, the allowed algorithms, and the verifier's expected issuer and audience. Once those line up, most "mysterious" JWT bugs collapse into one of a few predictable causes.
Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool