JWT Authentication: What's Inside the Token and How to Test It
JWTs pack auth and user context into a signed string. Simple on the surface, full of traps underneath.
What a JWT is
A JWT (JSON Web Token, pronounced "jot") is a signed, self-contained string that carries claims about a user or client, used to authenticate requests.
Self-contained means the server doesn't need to look up a session — the token itself has the user info and a signature proving it hasn't been tampered with. The server just verifies the signature and trusts the claims.
The three parts
A JWT is three Base64URL-encoded segments separated by dots:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQWxpY2UifQ.sig_bytes_here
↑ header ↑ payload ↑ signature
Decode the first two and you get JSON:
Header:
{ "alg": "HS256", "typ": "JWT" }
Payload (claims):
{
"sub": "user-123",
"email": "alice@example.com",
"role": "admin",
"iat": 1712345678,
"exp": 1712349278
}
Signature: HMAC-SHA256(header.payload, secret) (or RSA/ECDSA signature for asymmetric algorithms).
Anyone can decode a JWT — the payload is not encrypted, just signed. Never put secrets (passwords, credit cards) in a JWT.
Standard claims
Seven claims defined by RFC 7519:
| Claim | Meaning |
|---|---|
iss | Issuer — who created the token |
sub | Subject — who the token is about (user ID) |
aud | Audience — who the token is for |
exp | Expiration — Unix timestamp after which the token is invalid |
nbf | Not Before — Unix timestamp before which it's invalid |
iat | Issued At — when it was created |
jti | JWT ID — unique identifier for revocation |
Plus whatever custom claims your app needs (role, tenant_id, permissions).
The login flow
/auth/logincurl -X POST 'https://demo.totalshiftleft.ai/auth/login' \
-H 'Content-Type: application/json' \
-d '{"email":"demo@totalshiftleft.ai","password":"demo123"}'Response:
{
"access_token": "eyJhbGciOi...",
"refresh_token": "eyJhbGciOi...",
"expires_in": 900
}
Copy the access_token and use it to call protected endpoints:
/api/v1/mecurl -X GET 'https://demo.totalshiftleft.ai/api/v1/me'Add the header Authorization: Bearer eyJhbGciOi.... Without it, you get 401.
How the server verifies
On every request, the server:
- Reads the
Authorization: Bearer <token>header. - Splits the token into header, payload, signature.
- Checks
algmatches the expected algorithm (rejectnone!). - Recomputes the signature over
header.payloadusing the secret/public key. - Compares the computed signature to the provided signature (constant-time).
- Parses the payload, checks
expis in the future andnbfis in the past. - Optionally checks
iss,aud,jtiagainst a revocation list. - Only now treats the claims as trusted.
Skipping step 3 or 4 is the classic vulnerability.
Common JWT vulnerabilities
1. alg: none. Attacker changes the header to {"alg":"none"}, removes the signature, and forges any payload. Only happens if the library accepts none. Reject it explicitly.
2. alg confusion (RS256 → HS256). Attacker changes alg from RS256 to HS256 and signs with the server's public key as if it were the HMAC secret. Naive libraries verify it as HMAC with the public key (which is public) and accept it. Always pin the algorithm server-side; never trust the header's alg.
3. Weak HMAC secrets. 8-character secrets can be brute-forced offline from a single valid token. Use ≥256 bits of entropy.
4. Long expirations. exp 30 days in the future means a stolen token is usable for 30 days. Access tokens should live 15 minutes; long-lived refresh tokens do the rest.
5. No revocation. JWT is stateless by design, but that means revocation is hard. Solutions: short expiry + refresh tokens, jti blacklist, or per-user "tokens issued before X are invalid" timestamps.
6. Storing JWTs in localStorage. Vulnerable to XSS. Use httpOnly, Secure, SameSite cookies for browser clients.
7. Accepting expired tokens with clock skew. A 5-minute skew window is fine. A 24-hour skew window is a vulnerability.
What to test
Happy paths
- Login with valid creds → 200 with
access_token,refresh_token,expires_in. - Protected endpoint with valid token → 200.
- Token includes expected claims (
sub,email,role).
Authentication negatives
- No
Authorizationheader → 401. - Wrong scheme (
Basicinstead ofBearer) → 401. - Malformed token (missing a segment) → 401.
- Expired token (
expin the past) → 401 with distinguishable code (e.g.,TOKEN_EXPIRED). - Not-yet-valid token (
nbfin the future) → 401. - Token signed with wrong secret → 401.
alg: nonetoken → 401 (this is a security-critical test — most JWT libraries have had this bug).- Tampered payload (modify a claim, keep the old signature) → 401.
Authorization negatives
- Valid token with
role: userhitting admin endpoint → 403. - Valid token but
tenant_iddoesn't own the resource → 403 or 404 (document which).
Edge cases
- Token issued by a different
iss→ 401. - Token with
audnot matching this API → 401. - Token with huge payload (10 KB custom claims) → should work or 413.
- Multiple
Authorizationheaders → document behavior.
Refresh flow
- Valid refresh token → new access + refresh pair, old refresh token now invalid.
- Re-using an old refresh token → 401 (detects token theft).
- Refresh token after user password change → 401 (a password change should invalidate tokens).
What to look for when pen-testing JWTs
- Copy the JWT, change the
algtonone, remove the signature — does it still work? If yes, critical bug. - If
algis RS256, try resigning with HS256 using the server's public key — does it work? If yes, critical bug. - Decode the payload. Anything that shouldn't be there (passwords, PII, internal IDs that leak architecture)? Report as data exposure.
- Does the token include a
jti? If not, revocation is likely not implemented. - Set the system clock forward past
exp— does the token still work? Clock-skew vulnerability.
What's next
JWTs secure single-client auth well. For third-party delegated access, the standard is OAuth 2.0.
Related lessons
Simple, common, and surprisingly easy to get wrong. Here's the API key playbook.
OAuth 2.0's machine-to-machine flow. Clean, standard, and easy to test once you see the shape.
Short-lived access tokens need graceful refresh. Three common patterns, one big pitfall.
Read more on the blog
Complete guide to JWT authentication testing. Learn to validate token signatures, claims, expiration, and common JWT vulnerabilities in API security.
Master OAuth API testing with best practices for authorization code, client credentials, and token flows. Complete 2026 guide for secure OAuth implementation.