Web › Module 7 › Lesson 3
JWT Pitfalls
JWT is a bearer claim set — alg, secret, exp, and server checks still matter.
Visual · jwt_pitfalls
Three segments: header.payload.signature. Reject alg=none, weak secrets, missing exp. — router STOP — SAFE Own verifier only — no foreign token cracking.
Opening
JWTs fail open when verification is optional — that is still A07.
OWASP Top 10:2025 A07 Authentication Failures includes token misuse: accepting alg=none, confusing “decode” with “verify,” weak HMAC secrets, skipping exp/nbf, stuffing roles in the payload without server authority, and treating JWT as encrypting secrets (it is not confidentiality by default). Attacker goal: forge or replay a bearer token. Cyberlium: learn the pitfalls on tokens YOU mint on loopback — first; 0.0.1:8774. Not cracking stranger APIs, not none-attack toolkits against production IdPs, not hydra. Next: auth hardening lab. Today: JWT literacy and refuse lists.
1. Decode ≠ verify: signature and algorithm allowlists
Base64url-decoding a JWT shows claims; it does not prove integrity. Verification uses the configured algorithm and key. Allowlist algorithms (e.g., RS256 or HS256 — pick one deliberately). Reject alg=none and unexpected algs. Library misuse that “verifies” with an empty key fails A07.
Do not implement JWT crypto by hand for production. Use a maintained library on YOUR app. This lesson will not ship forge scripts against foreign issuers or live APIs.
Command guide
A07 Authn — Decode ≠ verify
═══ INSTALL ═══
Linux (Debian/Ubuntu):
Command — copy this
sudo apt install python3
macOS:
Command — copy this
brew install python3
Windows: Download https://python.org/downloads/
═══ COMMANDS ═══
Command — copy this
python3 - << PY
import base64, json
def b64(d):
return base64.urlsafe_b64encode(json.dumps(d, separators=(",", ":")).encode()).rstrip(b"=").decode()
tok = b64({"alg":"HS256","typ":"JWT"}) + "." + b64({"sub":"alice","aud":"cyberlium-lab"}) + ".not-a-signature"
print("sample", tok)
print("literacy: printing payload != accepting the token")
print("ship: verify signature + algorithm allowlist")
PYCommand — copy this
python3 - << 'PY'
import base64, json
def pad(s):
return s + "=" * ((4 - len(s) % 4) % 4)
header = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" # public example header only
print(json.loads(base64.urlsafe_b64decode(pad(header))))
print("decode is not verify; refuse skip-signature on MY verifier")
PY2. Secrets and keys: strength, rotation, not in the repo
HMAC secrets must be long and random. Asymmetric keys need private material offline from clients. Rotate with kid if you support multiple keys. Never commit production secrets. Notes may say “secret in env” — never paste the live secret into cyberlium-lab files.
“jwt” as the HMAC secret is a classic failure mode in demos — refuse it on any app you ship.
Command guide
A07 Authn — Secrets and keys
═══ INSTALL ═══
Linux (Debian/Ubuntu):
Command — copy this
sudo apt install curl
macOS: Built-in
Windows: Built-in (PowerShell: Invoke-WebRequest)
═══ COMMANDS ═══
Command — copy this
SAFE="http://127.0.0.1:8774"
Command — copy this
curl -sS -m 3 -I "$SAFE/" | head -n 10
3. Claims: exp, iss, aud, and roles that are not gospel
Always validate exp (and nbf if used). Validate iss/aud when multiple services share tokens. Roles in the payload are claims — your API still authorizes against server policy (authn ≠ authz). A forged role claim with a valid signature from a stolen secret is catastrophic; protect the secret. A role claim without signature verification is theater.
Short TTLs + refresh rotation beat immortal access tokens. Document your TTLs for the loopback app.
4. Storage: XSS vs CSRF tradeoffs — still no steal kits
Browser storage choices (memory, HttpOnly cookie, localStorage) trade XSS and CSRF risk. Prefer patterns your framework documents for SPAs/BFF. Do not build cookie-steal demos. Do not exfiltrate tokens from classmates. Do not hydra the router for a “real” JWT.
Cite A07. Original Cyberlium — not official OWASP certification.
5. Wrong vs right: forge foreign APIs vs verify your own tokens
Same word “JWT,” opposite job.
Wrong
Attack production IdPs with none/alg confusion kits. Crack stranger tokens. Hydra 192.168.0.1. Commit live secrets. Treat decode as verify. Skip exp.
Right
Identify DEMO; router → SAFE. Allowlist alg; verify signature; validate exp/iss/aud; strong secrets in env; lock jwt-pitfalls-notes.txt on YOUR app. Next: Lab — Auth Hardening on Loopback.
6. Hands-on: identify DEMO + jwt-pitfalls-notes.txt
The toy on 8774 prints a refuse checklist and a reminder endpoint — it does not forge against networks or live APIs.
Mission: jwt-pitfalls-notes.txt (mode 600)
1) 0.0.1:8774 after starting a07_jwt_refuse.py.2) Document alg allowlist, exp/iss/aud checks, and secret storage (no live secret text). Write one authn≠authz sentence for role claims. 3) chmod 600 $HOME/cyberlium-lab/jwt-pitfalls-notes.txt. No foreign forge kits. No hydra. No nmap.
Stuck? Ask Cyberlium AI Mentor
If JWT still means “crack the token,” ask for a hint — not a forge script. Try: "Hint only: decode vs verify; why alg allowlists; why exp matters; why 192.168.0.1 router login is OUT OF SCOPE; where notes live?" You still fill jwt-pitfalls-notes.txt.
JWTs are bearer claims that fail when verification is optional. A07 Authentication Failures. Original Cyberlium — not official OWASP certification. Next — Lab — Auth Hardening on Loopback.
Knowledge Check
APPLY: A library call only base64-decodes a JWT and trusts role=admin. What failed?
Multiple choice
Knowledge Check
APPLY: True or False: A JWT payload is encrypted by default, so secrets in claims are safe.
True or False
Knowledge Check
APPLY: curl of http://192.168.0.1/ is router admin. Forge a JWT against it for A07?
Multiple choice