JWT Authentication Debugging: Decode, Verify, and Fix Token Issues
Learn JWT structure, how claims work, why tokens expire, and how to debug common JWT authentication failures in your applications.
JWT Authentication Debugging: Decode, Verify, and Fix Token Issues
JSON Web Tokens are the backbone of modern API authentication. They're compact, self-contained, and stateless. They're also a frequent source of cryptic auth failures. Understanding their structure is the fastest path from "401 Unauthorized" to a working application.
What Is a JWT?
A JSON Web Token (JWT) is a compact, URL-safe token format defined in RFC 7519. It consists of three Base64URL-encoded parts separated by dots:
header.payload.signature
A real token looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header
The header specifies the token type and signing algorithm:
{
"alg": "HS256",
"typ": "JWT"
}
Common algorithms: HS256 (HMAC-SHA256), RS256 (RSA-SHA256), ES256 (ECDSA-SHA256).
Payload (Claims)
The payload contains claims — statements about the subject:
{
"sub": "1234567890",
"name": "Alice",
"email": "alice@example.com",
"roles": ["admin"],
"iat": 1516239022,
"exp": 1516242622,
"iss": "https://auth.example.com",
"aud": "https://api.example.com"
}
Signature
The signature verifies the token hasn't been tampered with:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Critical: The payload is Base64URL-encoded, not encrypted. Anyone can decode and read the payload. Never put sensitive data (passwords, credit card numbers, PII beyond what's necessary) in a JWT.
Registered Claims (RFC 7519)
| Claim | Full Name | Description |
|---|---|---|
iss |
Issuer | Who issued the token |
sub |
Subject | Who the token is about (usually user ID) |
aud |
Audience | Who the token is intended for |
exp |
Expiration | Unix timestamp after which the token is invalid |
nbf |
Not Before | Unix timestamp before which the token is invalid |
iat |
Issued At | Unix timestamp when the token was issued |
jti |
JWT ID | Unique identifier for this token (prevents replay) |
Common JWT Authentication Failures
1. Token Expired (exp in the past)
{ "error": "jwt expired" }
Check the exp claim. Convert the Unix timestamp:
new Date(1516242622 * 1000).toISOString()
// "2018-01-18T02:30:22.000Z"
Short expiration windows (15 minutes) are a security best practice but require a refresh token flow to maintain user sessions.
2. Algorithm Confusion Attack
If your server accepts both RS256 and HS256, an attacker can take the RS256 public key (which is public) and use it as the HMAC secret to forge tokens signed with HS256.
Fix: Always specify the allowed algorithm explicitly, never accept none as an algorithm:
jwt.verify(token, publicKey, { algorithms: ['RS256'] });
3. Audience Mismatch
A token issued for https://api-v1.example.com should not be accepted by https://api-v2.example.com. Validate the aud claim:
jwt.verify(token, secret, { audience: 'https://api-v2.example.com' });
4. Clock Skew
Tokens validated across servers with different system clocks can fail due to exp or nbf being milliseconds off. Most JWT libraries have a clockTolerance option (typically ±30 seconds).
5. Signature Verification Failure
A "invalid signature" error means either:
- The wrong secret/key is being used
- The token was modified after signing
- The Base64URL encoding was corrupted (watch out for URL-decoded
+and/characters)
JWT Security Best Practices
- Use short expiration times (15–60 minutes for access tokens).
- Use refresh tokens stored in
httpOnlycookies, not localStorage. - Validate
iss,aud, andexpon every request. - Use RS256 or ES256 for distributed systems where multiple services verify tokens — avoids sharing a secret.
- Never put sensitive data in the payload — it's readable by anyone with the token.
- Implement token revocation for high-security flows (maintain a token blocklist or use short expiration).
Debug JWTs in Your Browser
The JWT Debugger on InfraHub decodes any JWT instantly, shows the header, payload, and claims in a readable format, checks expiration status, and validates the signature if you provide the secret or public key. It runs entirely in your browser — your tokens are never transmitted to any server.
Paste in a token from your auth flow, a test environment, or an API documentation example to verify its structure before writing validation code.