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

JWT Deep Dive

cryptography PHP 7.0+ Advanced

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 37
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 1 ping M 0 pings T 0 pings W 0 pings T 1 ping F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T 2 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W 0 pings T 2 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 0 pings T
No pings yet today
No pings yesterday
Amazonbot 11 Perplexity 9 Unknown AI 3 Ahrefs 3 Google 2 SEMrush 2 Majestic 1
crawler 30 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