How to Debug a JWT Token Step by Step
A practical guide to reading, validating, and troubleshooting JSON Web Tokens when authentication breaks.
Every developer working with modern APIs will eventually stare at a JWT token wondering why authentication is failing. The token looks like a wall of random characters. The API returns 401. The logs say "token expired" or "invalid signature" or, worse, nothing at all.
Here is how to debug a JWT systematically, from decoding the payload to catching the most common mistakes.
What a JWT actually contains
A JWT is three Base64-encoded sections separated by dots. The first section is the header (algorithm and token type). The second is the payload — the actual claims like user ID, roles, and expiration. The third is the signature, which proves the token was not tampered with.
When you paste a JWT into a decoder, you are reading the header and payload. You do not need the secret key for this. The payload is not encrypted, just encoded. Anyone with the token can read it.
Step 1: Decode the token
Paste the full token (all three dot-separated sections) into a JWT decoder. Check these fields immediately:
The exp claim is the expiration time as a Unix timestamp. If this time has passed, the token is expired. This is the single most common cause of 401 errors. Use a timestamp converter to turn the number into a readable date if needed.
The iat (issued at) and nbf (not before) claims define when the token becomes valid. If your server clock is wrong, a freshly-issued token might appear to be from the future.
The iss (issuer) and aud (audience) claims must match what your API expects. A token issued by auth.staging.example.com will fail validation against auth.example.com.
Step 2: Check the algorithm
The header contains an alg field. Common values are HS256 (HMAC with SHA-256, symmetric), RS256 (RSA with SHA-256, asymmetric), and ES256 (ECDSA, asymmetric).
If your API expects RS256 but the token header says HS256, validation will fail. This mismatch can happen when switching between auth providers or environments. It can also be an attack vector: the "alg: none" attack tricks some libraries into skipping signature verification entirely. Check this field deliberately, not as an afterthought.
Step 3: Verify the claims
Look at the custom claims in the payload. These are application-specific fields like role, permissions, tenant_id, or scope. Common issues include the user having role: viewer when the endpoint requires role: admin, the scope field missing a required permission, and a tenant_id that does not match the resource being accessed.
Step 4: Check token transmission
How is the token being sent? The standard is the Authorization: Bearer <token> header. Common mistakes include sending it as a query parameter (some APIs reject this), including the word "Bearer" twice, having whitespace or newlines in the token (happens when copying from logs), and the token being URL-encoded when it should not be.
Step 5: Clock skew
If the token looks valid but authentication still fails, check the server time. A 5-minute clock difference between the auth server and the API server can cause tokens to appear expired or not-yet-valid. Most JWT libraries have a clockTolerance or leeway setting for this. It is worth configuring even when you do not think you need it.
Step 6: Signature verification
If the payload looks correct and everything else checks out, the issue might be the signature. This means the secret key or public key used to verify the token does not match the one used to sign it. This happens after key rotation, when using different keys per environment, or when the JWKS (JSON Web Key Set) endpoint is cached and stale.
Quick debugging checklist
- Check
expclaim against current time - Verify
issandaudmatch your configuration - Confirm the
algheader matches what your API expects - Inspect custom claims for correct roles and permissions
- Verify the Authorization header format is correct
- Check server clock synchronization
- Confirm the signing key matches between issuer and verifier
Tools
- JWT Decoder — decode any JWT safely in your browser, no data sent to a server
- Timestamp Converter — convert Unix timestamps from JWT claims to readable dates
- Base64 Encoder & Decoder — decode individual JWT sections manually