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

JWT Algorithm Confusion (alg:none & RS→HS)

Security CWE-327 OWASP A2:2021 CVSS 9.1 PHP 5.0+ Intermediate
debt(d5/e3/b3/t9)
d5 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm as the tools, and the code_pattern describes conditions like 'JWT verification without specifying allowed algorithms' — these are patterns a SAST tool (semgrep) or static analyser (psalm) can flag, but a standard linter won't catch them by default. The flaw is invisible at runtime in normal operation until an attacker exploits it.

e3 Effort Remediation debt — work required to fix once spotted

Closest to 'simple parameterised fix' (e3). The quick_fix says 'always specify an allowlist of permitted algorithms when verifying JWTs' — this is a targeted configuration change or a small code update at the verification call site, not a one-liner swap but also not a multi-file refactor. Switching to a well-maintained library like firebase/php-jwt and passing the algorithm allowlist parameter is contained within the JWT verification logic.

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

Closest to 'localised tax' (b3). The applies_to scope is web and API contexts, and the fix is confined to JWT verification points in the codebase. While authentication is load-bearing, the algorithmic allowlist fix is typically concentrated in one or a few verification helpers rather than spread across every file — it does not define the system's shape or impose a persistent productivity tax on unrelated work streams.

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

Closest to 'catastrophic trap' (t9). The misconception field states explicitly that developers believe 'any JWT library handles the alg:none attack automatically' — this is exactly backwards. Older and poorly maintained libraries actively accept attacker-controlled algorithm values unless explicitly restricted. The RS256→HS256 confusion is an additional catastrophic inversion where the public key becomes the HMAC secret. The 'obvious' default behaviour (trust the library, trust the token header) is always wrong in this context.

About DEBT scoring →

Also Known As

JWT none algorithm alg:none attack JWT signature bypass

TL;DR

Forging JWTs by exploiting libraries that accept alg:none or by switching an RS256 public key into HMAC-SHA256 signing.

Explanation

Two classic JWT attacks exploit algorithm flexibility. The alg:none attack: some libraries accept a token with {'alg':'none'} and no signature, treating it as valid — an attacker can forge any payload. The algorithm confusion attack: an RS256 token (asymmetric — signed with private key, verified with public key) is downgraded to HS256 (symmetric HMAC). If the library reuses the public key as the HMAC secret, an attacker who knows the public key can sign arbitrary tokens. Defences: always specify the expected algorithm explicitly in the verify call; never accept algorithm from the token header; use a dedicated JWT library (firebase/php-jwt, lcobucci/jwt) that enforces algorithm allowlisting; reject tokens with alg:none unconditionally.

Common Misconception

Any JWT library handles the alg:none attack automatically. Older and poorly maintained libraries still accept tokens with "alg":"none" unless explicitly configured to reject them — always specify an allowlist of accepted algorithms.

Why It Matters

The 'none' algorithm tells the server the token is unsigned, and some libraries accept it — an attacker can forge any token with any claims without knowing the secret.

Common Mistakes

  • Using a JWT library that accepts whatever algorithm is specified in the token header without validating against an allowlist.
  • Not explicitly specifying the expected algorithm when verifying — letting the token dictate its own verification.
  • Mixing HS256 and RS256 — sending an HS256 token to a server expecting RS256 can cause the public key to be used as the HMAC secret.
  • Trusting the decoded payload before verifying the signature — some libraries return data even on verification failure.

Code Examples

✗ Vulnerable
JWT::decode($token, $key); // accepts whatever alg the token claims
✓ Fixed
JWT::decode($token, new Key($key, 'RS256')); // algorithm pinned explicitly

Added 15 Mar 2026
Edited 22 Mar 2026
Views 53
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 2 pings T 0 pings W 0 pings T 2 pings F 1 ping S 1 ping S 0 pings M 1 ping T 0 pings W 0 pings T 0 pings F 0 pings S 1 ping S 0 pings M 0 pings T 0 pings W 1 ping T 1 ping F 0 pings S 1 ping S 0 pings M 1 ping T 0 pings W
No pings yet today
Ahrefs 1
Amazonbot 9 Perplexity 5 Google 4 Ahrefs 4 SEMrush 4 Scrapy 4 Unknown AI 3 Bing 3 Majestic 1 Claude 1 PetalBot 1
crawler 36 crawler_json 2 pre-tracking 1
DEV INTEL Tools & Severity
🔴 Critical ⚙ Fix effort: Low
⚡ Quick Fix
Always specify an allowlist of permitted algorithms when verifying JWTs — never accept 'none' as a valid algorithm; use a well-maintained library like firebase/php-jwt
📦 Applies To
PHP 5.0+ web api
🔗 Prerequisites
🔍 Detection Hints
JWT verification without specifying allowed algorithms; accepting alg header from token without validation; custom JWT decode without algorithm check
Auto-detectable: ✓ Yes semgrep psalm
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Low ✓ Auto-fixable Fix: Low Context: Line Tests: Update
CWE-347 CWE-327


✓ schema.org compliant