Unit 7: Logins, MFA and tokens · Lesson 6 of 7 · 22 min

Reading JWTs

Decode a token, read its claims and check that the API enforces them.

mission

You are
The analyst reviewing the shop's API sign-in.
Situation
The shop's mobile app gets a token from /api/token and sends it to the API. Tokens are meant to expire after 15 minutes.
Goal
Decode the token, read its claims and test whether the API respects the expiry.
Hand in
The token's claims and a finding if expiry is not enforced.

A JWT

eyJhbGci....eyJpc3Mi....d08sbwAE...
  1. 1eyJhbGci...header: algorithm and type
  2. 2eyJpc3Mi...payload: the claims, readable by anyone
  3. 3d08sbwAE...signature: proves the server issued it
01

Get a token

learner@web-lab
curl -s -d @shop-account.txt http://127.0.0.1:8000/api/token | jq .
{
  "token_type": "Bearer",
  "expires_in": 900,
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3Nob3AuaW50ZXJuYWwiLCJzdWIiOiJsZWFybmVyIiwicm9sZSI6ImN1c3RvbWVyIiwiaWF0IjoxNzkxOTY4NDAwLCJleHAiOjE3OTE5NjkzMDB9.d08sbwAEhs0DgnBier-XpQ_DfyGE8sFcvvl7UTcpomo"
}
curl -s -d @shop-account.txt http://127.0.0.1:8000/api/token | jq -r .access_token > token.txt

you should see A bearer token valid for 900 seconds.

02

Decode the header and payload

The parts are base64url, not encrypted. Anyone holding the token can read them.

learner@web-lab
cut -d. -f1 token.txt | jq -R '@base64d | fromjson'
{
  "alg": "HS256",
  "typ": "JWT"
}
cut -d. -f2 token.txt | jq -R '@base64d | fromjson'
{
  "iss": "https://shop.internal",
  "sub": "learner",
  "role": "customer",
  "iat": 1791968400,
  "exp": 1791969300
}

you should see HS256, then issuer, subject, role and two timestamps.

defender tip Never put secrets or personal data in a JWT payload. Signing stops changes, not reading.

03

When does it expire?

learner@web-lab
cut -d. -f2 token.txt | jq -R '@base64d | fromjson | .exp | todate'
"2026-10-14T09:15:00Z"

you should see It expired at 09:15 on 14 October 2026, fifteen minutes after it was issued and long before today.

04

Does the API care?

learner@web-lab
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $(cat token.txt)" http://127.0.0.1:8000/api/v1/me
200

you should see 200. The API checks the signature but not exp, so a leaked token works forever.

Token checks

Shop API today
claims = verify_signature(token)

Expired tokens still work.

Fix
claims = verify_signature(token)
if claims["exp"] < now(): reject()
if claims["iss"] != ISSUER: reject()

Check expiry, issuer and audience on every request.

What a defender takes away

  1. 1A JWT's payload is readable by anyone who holds it.
  2. 2APIs must check signature, expiry, issuer and audience.
  3. 3Short expiry only helps if the server enforces it.

Questions

0 of 3 solved

Answer each one from what you find on your lab machine.