How to Validate a JWT Token: Signature, Expiry & Claims
Validating a JWT is not optional — it is the entire security model. A token that is merely decoded (base64-decoded) but not verified can be forged by anyone. This guide covers what proper validation means and how to do it.
Decode vs Verify
Decoding reads the header and payload without checking authenticity. Anyone can base64-decode a JWT and read its claims.
Verifying checks the cryptographic signature using your secret (HS256) or public key (RS256). Only verified tokens should be trusted.
// WRONG — only decodes, does not verify
const payload = jwt.decode(token);
// CORRECT — verifies signature and claims
const payload = jwt.verify(token, secret, { algorithms: ['HS256'] });Step 1: Verify the Signature
For HS256 tokens, verification requires the same secret used to sign the token:
const jwt = require('jsonwebtoken');
try {
const payload = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // always specify — prevents alg confusion
});
console.log('Valid token:', payload);
} catch (err) {
console.error('Invalid token:', err.message);
}Quick check: Paste your token and secret into the JWT Validator to see signature status, decoded claims, and expiration at a glance.
Step 2: Check Expiration
The exp claim is a Unix timestamp after which the token is no longer valid. Most JWT libraries check this automatically during verify().
jwt.verify(token, secret, {
algorithms: ['HS256'],
maxAge: '15m', // additional server-side limit
});If a token is expired, reject it immediately — do not attempt to refresh it server-side without a valid refresh token flow.
Step 3: Validate Issuer and Audience
jwt.verify(token, secret, {
algorithms: ['HS256'],
issuer: 'https://auth.yourapp.com',
audience: 'https://api.yourapp.com',
});Without audience validation, a token issued for one service can be replayed against another.
Step 4: Inspect Claims for Authorization
After verification, use claims like sub (user ID) and role for authorization decisions. Never trust unverified claims.
Common Validation Errors
| Error | Cause | Fix |
|---|---|---|
| invalid signature | Wrong secret or tampered payload | Check secret, re-sign token |
| jwt expired | `exp` in the past | Issue a new token |
| invalid algorithm | alg mismatch or confusion attack | Specify `algorithms` explicitly |
| jwt audience invalid | Token not for this service | Set and validate `aud` |
Debugging with Browser Tools
During development, use the JWT Validator to paste tokens and secrets without writing debug code. For building test tokens, use the JWT Encoder.
Express Middleware Example
Wrap validation in middleware so every protected route verifies tokens consistently:
const jwt = require('jsonwebtoken');
function authMiddleware(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing token' });
}
const token = header.slice(7);
try {
req.user = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
audience: 'https://api.yourapp.com',
issuer: 'https://auth.yourapp.com',
});
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
app.get('/api/profile', authMiddleware, (req, res) => {
res.json({ userId: req.user.sub });
});Never skip verification on "internal" routes — attackers probe unprotected endpoints first.
Refresh Token Pattern
Short-lived access tokens (15 minutes) limit exposure if stolen. Pair them with refresh tokens stored in httpOnly cookies:
1. Client sends access token with each API request
2. When access token expires, client calls /auth/refresh with the refresh token
3. Server issues a new access token (and optionally rotates the refresh token)
4. Server validates the refresh token against a database or Redis store (refresh tokens are typically opaque, not JWTs)
Access token validation remains stateless (signature check only). Refresh token validation requires server-side lookup — this is intentional.
Production Validation Checklist
Before shipping, confirm your API:
- [ ] Specifies
algorithmsexplicitly on everyverify()call - [ ] Validates
exp(most libraries do this by default) - [ ] Sets and checks
audandissclaims - [ ] Rejects tokens with unexpected
algvalues in the header - [ ] Uses HTTPS for all token transmission
- [ ] Does not log full tokens in application logs
- [ ] Has a secret rotation plan documented
Use the JWT Validator during QA to test edge cases: expired tokens, wrong secrets, tampered payloads, and missing claims.
Security Reminders
- Always specify the expected algorithm in
verify()options - Use short-lived access tokens (15–60 minutes)
- Never log full tokens in production
- Rotate secrets if you suspect compromise — see key rotation guide
Related Guides
- JWT best practices checklist
- Common JWT vulnerabilities
- What is JWT — structure and claims explained
Quick Debugging Tips
If validation fails in production but works locally, check these common causes:
1. Whitespace in the secret — copy-paste from env files can add trailing newlines
2. Wrong environment — staging token verified against production secret
3. Clock skew — server clocks more than 60 seconds apart can cause false exp failures
4. Algorithm mismatch — token signed with HS256 but verified with RS256 public key
Paste failing tokens into the JWT Validator with the suspected secret to isolate whether the issue is signature, expiration, or claim validation.