intermediate·8 min read·Updated May 1, 2026

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:

ClaimMeaning
issIssuer — who created the token
subSubject — who the token is about (user ID)
audAudience — who the token is for
expExpiration — Unix timestamp after which the token is invalid
nbfNot Before — Unix timestamp before which it's invalid
iatIssued At — when it was created
jtiJWT ID — unique identifier for revocation

Plus whatever custom claims your app needs (role, tenant_id, permissions).

The login flow

POST/auth/login
Log in and receive a JWT.
curl -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:

GET/api/v1/me
Call a protected endpoint with Authorization: Bearer <token>.
curl -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:

  1. Reads the Authorization: Bearer <token> header.
  2. Splits the token into header, payload, signature.
  3. Checks alg matches the expected algorithm (reject none!).
  4. Recomputes the signature over header.payload using the secret/public key.
  5. Compares the computed signature to the provided signature (constant-time).
  6. Parses the payload, checks exp is in the future and nbf is in the past.
  7. Optionally checks iss, aud, jti against a revocation list.
  8. 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 Authorization header → 401.
  • Wrong scheme (Basic instead of Bearer) → 401.
  • Malformed token (missing a segment) → 401.
  • Expired token (exp in the past) → 401 with distinguishable code (e.g., TOKEN_EXPIRED).
  • Not-yet-valid token (nbf in the future) → 401.
  • Token signed with wrong secret → 401.
  • alg: none token → 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: user hitting admin endpoint → 403.
  • Valid token but tenant_id doesn't own the resource → 403 or 404 (document which).

Edge cases

  • Token issued by a different iss → 401.
  • Token with aud not matching this API → 401.
  • Token with huge payload (10 KB custom claims) → should work or 413.
  • Multiple Authorization headers → 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

  1. Copy the JWT, change the alg to none, remove the signature — does it still work? If yes, critical bug.
  2. If alg is RS256, try resigning with HS256 using the server's public key — does it work? If yes, critical bug.
  3. Decode the payload. Anything that shouldn't be there (passwords, PII, internal IDs that leak architecture)? Report as data exposure.
  4. Does the token include a jti? If not, revocation is likely not implemented.
  5. 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

Read more on the blog