← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

JWT Deep Dive

Cryptography PHP 7.0+ Advanced
debt(d7/e5/b5/t9)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7), because while semgrep and psalm are listed as detection tools and can catch specific patterns like base64_decode without verification or alg:none acceptance, these require custom rules and specialist configuration. The most dangerous variants (algorithm confusion, missing claim validation) are not caught by default linters and often slip through automated scanning unless rules are explicitly written for JWT misuse patterns — making careful code review the realistic detection path for most teams.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'touches multiple files / significant refactor in one component' (e5), because the quick_fix involves not just a one-line patch but a coordinated set of changes: switching to a vetted library, whitelisting algorithms explicitly, adding claim validation (exp/iat/iss/aud), and potentially moving tokens from localStorage to HttpOnly cookies. This typically touches middleware, auth helpers, token issuance, and token validation code across multiple files in one component.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5), because JWT auth applies to web and API contexts broadly and the chosen validation approach (algorithm whitelisting, claim validation strategy, storage mechanism) becomes a persistent pattern that shapes every authenticated endpoint and every future developer who touches auth code. It is not load-bearing across the entire system like a database choice, but it is a sustained tax on all auth-related work streams.

t9 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'catastrophic trap' (t9), because the canonical misconception — that a valid JWT signature means the token is trustworthy — is precisely the 'obvious' belief that is always wrong. The term's misconception field states explicitly that the signature is only valid relative to a key and algorithm, and the alg:none and RS256→HS256 confusion attacks show that the intuitive trust in a 'signed' token leads directly to critical auth bypass. This contradicts how most developers reason about cryptographic signatures and the failure mode is silent, complete authentication bypass in production.

About DEBT scoring →

Also Known As

JWT JSON Web Token JWS JWE Bearer token

TL;DR

JSON Web Tokens carry signed (or encrypted) claims — three Base64url-encoded sections: header.payload.signature. Security depends entirely on algorithm choice and verification correctness.

Explanation

JWT structure: header (algorithm + type), payload (claims: sub, exp, iat, iss, custom), signature (HMAC-SHA256 or RSA/ECDSA). Signed JWTs (JWS) prevent tampering but the payload is readable by anyone. Encrypted JWTs (JWE) also hide the payload. Critical security: always validate exp (expiry), iss (issuer), aud (audience). Never accept algorithm=none. Prefer RS256 (asymmetric) for public verification over HS256 (shared secret). Store in HttpOnly cookies, not localStorage.

Common Misconception

A valid JWT signature means the token is trustworthy — a signature is only valid relative to a key; if the algorithm is none or the key is weak, the signature provides no security.

Why It Matters

JWT algorithm confusion (accepting none or RS→HS algorithm switching) is a critical vulnerability that has affected many frameworks — understanding JWT security prevents this entire class of auth bypass.

Common Mistakes

  • Not validating exp claim — expired tokens accepted indefinitely after logout.
  • Accepting algorithm: none — allows unsigned tokens; always whitelist accepted algorithms explicitly.
  • Algorithm confusion: RS256 → HS256 — attacker signs with public key as HMAC secret; validate alg header.
  • JWT in localStorage — XSS-accessible; use HttpOnly cookies with SameSite=Strict.

Avoid When

  • You need to invalidate tokens before expiry — JWTs are stateless and cannot be revoked without a server-side denylist.
  • The payload contains sensitive data — JWT payloads are base64-encoded, not encrypted; anyone can decode them.
  • Using the alg:none vulnerability window — always validate the algorithm header and reject none.
  • Long-lived JWTs — a stolen token is valid until expiry; keep access tokens short (15 min) and use refresh tokens.

When To Use

  • Stateless authentication across multiple services where a shared session store is not feasible.
  • API authentication where the client stores the token and sends it with each request.
  • Single sign-on (SSO) flows where identity is delegated from an auth server to resource servers.
  • Short-lived service-to-service tokens where the overhead of a token introspection call is undesirable.

Code Examples

✗ Vulnerable
// Algorithm confusion vulnerability:
function validateJwt(string $token, string $publicKey): array {
    $parts = explode('.', $token);
    $header = json_decode(base64_decode($parts[0]), true);
    $alg = $header['alg']; // Attacker sets alg=HS256
    // Uses public key as HMAC secret — attacker can sign!
    return verifySignature($token, $publicKey, $alg);
}
✓ Fixed
// Whitelist algorithm, validate all claims:
use Firebase\JWT\JWT;
use Firebase\JWT\Key;

// Always specify allowed algorithms explicitly:
$decoded = JWT::decode(
    $token,
    new Key($publicKey, 'RS256') // Algorithm whitelist — rejects HS256, none
);

// Validate critical claims:
if ($decoded->iss !== 'https://auth.example.com') throw new Exception('Invalid issuer');
if ($decoded->aud !== 'api.example.com') throw new Exception('Invalid audience');
if ($decoded->exp < time()) throw new Exception('Token expired');

Added 15 Mar 2026
Edited 25 Mar 2026
Views 74
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 1 ping T 1 ping F 1 ping S 0 pings S 0 pings M 0 pings T 1 ping W 1 ping T 5 pings F 1 ping S 5 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 1 ping M 1 ping T 0 pings W
No pings yet today
Sogou 1
Amazonbot 13 Scrapy 12 Perplexity 9 Ahrefs 5 SEMrush 4 Google 3 Unknown AI 3 Bing 2 ChatGPT 2 Majestic 1 Claude 1 Meta AI 1 Qwen 1 PetalBot 1 Sogou 1
crawler 56 crawler_json 2 pre-tracking 1
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Medium
⚡ Quick Fix
Always verify the algorithm explicitly (do not trust the 'alg' header), validate exp/iat/iss/aud claims, and use a vetted library — never decode and trust without verification
📦 Applies To
PHP 7.0+ web api
🔗 Prerequisites
🔍 Detection Hints
base64_decode on JWT without signature verification; accepting alg:none; no exp claim validation
Auto-detectable: ✓ Yes semgrep psalm
⚠ Related Problems
🤖 AI Agent
Confidence: Medium False Positives: Medium ✗ Manual fix Fix: Medium Context: File Tests: Update
CWE-347 CWE-345


✓ schema.org compliant