Understanding JWT Structure: The Key to Debugging

A JSON Web Token (JWT) has three parts separated by dots: the header, the payload, and the signature. Each part is Base64URL-encoded, which means you can decode and inspect them without any special tools. This transparency is what makes JWT debugging manageable. The header typically contains two fields: alg (the signing algorithm) and typ (the token type, usually JWT). The payload contains the claims: standard claims like sub, exp, iat, and custom claims specific to your application. The signature is the cryptographic proof that the token has not been tampered with. When debugging JWT issues, always start by decoding the token.

Common JWT Errors and Their Root Causes

The most frequent JWT error developers encounter is TokenExpiredError, which occurs when the exp claim contains a Unix timestamp earlier than the current server time. Clock skew between servers—often 30-60 seconds in distributed systems—causes tokens that appear valid on the issuing server to be rejected by the verifying server. Set a clock tolerance (e.g., clockTolerance: 60 in jsonwebtoken for Node.js) to compensate. Another common error is JsonWebTokenError: invalid signature, which typically stems from one of three causes: using the wrong secret key (development vs. production), mismatched signing algorithms between issuer and verifier, or the token being modified somewhere in transit—including apparently harmless changes like adding whitespace or reordering JSON keys in the payload. The NotBeforeError with the nbf claim is less common but equally frustrating: it means the token was issued with a future activation time, usually due to a server whose clock is ahead of the verifying server.

Deep Dive: Algorithm Confusion Attacks

One of the most dangerous JWT vulnerabilities is the algorithm confusion attack. If your server blindly trusts the alg field in the JWT header, an attacker can craft a token with "alg": "none" and an empty signature, which naive verifiers accept as valid without checking any cryptographic proof. A subtler variant sets "alg": "HS256" when the server expects RS256: the attacker signs the token with the RSA public key (which is typically public and known to all clients) as if it were an HMAC secret. Because the public key is a string and HMAC accepts any string as a secret, the server's verification succeeds. The fix is twofold: always maintain a whitelist of allowed algorithms in your JWT library (e.g., algorithms: ['RS256', 'ES256']), and never pass the algorithm from the token header directly to the verification function without validation. Modern JWT libraries like jsonwebtoken@9+ for Node.js and PyJWT@2+ for Python reject "none" algorithm by default, but you must still explicitly configure the allowed asymmetric algorithms.

Debugging Workflow: From Error to Fix

Start every JWT debugging session by pasting the token into a decoder—our JWT Decoder tool splits the header, payload, and signature and Base64URL-decodes each part. Check the header first: confirm the alg field matches what your server expects, note the kid (Key ID) if present for multi-key setups, and verify there are no unexpected fields. Next, inspect the payload claims: exp should be a future timestamp (check at epochconverter.com), iat should be in the past but not unreasonably old, iss should match the expected issuer domain, and aud should match your application's expected audience string.

If the decoded payload looks correct but signature verification fails, the problem is in the cryptographic layer. In Node.js, explicitly set algorithms in jwt.verify() options. For RS256, ensure you are using the correct PEM-formatted public key, not the private key. For HS256, verify the secret string is byte-for-byte identical on both sides—encoding differences like UTF-8 vs. Latin-1 or trailing newlines in environment variables are silent killers. When debugging in a microservice environment, check whether the token passes through an API gateway that strips or modifies headers; some gateways truncate the Authorization: Bearer <token> header if it exceeds a length limit.

JWT Best Practices and Pitfalls

Never store sensitive information in the JWT payload. The payload is Base64URL-encoded, not encrypted—anyone who intercepts the token can decode and read its contents. Use opaque reference tokens or JWE (JSON Web Encryption) for truly confidential data. Keep tokens short-lived: 15-30 minutes for access tokens, with refresh tokens for longer sessions. This limits the damage window if a token is leaked. Implement token revocation either through a server-side blocklist (storing revoked jti values in Redis with a TTL matching the token's remaining lifetime) or by issuing short-lived tokens with no revocation needed. Set the aud (audience) claim to prevent token reuse across different services. A token issued for your billing service should not be accepted by your user profile service, even if both share the same signing key.

Common JWT Pitfalls and How to Avoid Them

The three most frequent JWT mistakes are: using none algorithm (trivially forgeable), storing secrets in the payload instead of the header, and forgetting to validate the aud claim. Always enforce a strict algorithm whitelist on the server side — never let the client dictate which algorithm to use during verification. A simple allowed_algs = ['HS256', 'RS256'] check before calling jwt.decode() prevents the majority of JWT attacks in a single line of code.

Work with JSON Like a Pro

Our free online tools help you format, validate, compare, and transform JSON in seconds — no signup, no downloads, 100% private.