The algorithm called none
The flexibility of this format is an attacker's favourite feature, and the famous case is the algorithm called none.
The specification allows an unsigned token. In 2015 a researcher showed that several major libraries accepted a token whose header claimed none and whose signature was empty, treating verification as passed.
Forging a session became this easy: decode the payload, edit the user identifier, set the algorithm to none, strip the signature. Libraries patched it, and the lesson generalises.
Recognise the general form. That header is attacker-controlled input, and letting it choose your verification algorithm means running the attacker's instructions. Pin an allowed list of algorithms on your side, always.
Meet its subtler cousin, key confusion. Your service verifies with a public key, so the attacker crafts a token signed with a shared-secret algorithm, using your public key, published by design, as that secret.
A library that lets the header pick the family will verify that forgery with the very key you published. Same root cause, same fix: your verifier decides the algorithm, never the token.
What actually fills pentest reports
Expect the unglamorous findings that actually fill penetration test reports. Missing audience and issuer checks, so a token issued by the same provider for a different app logs into yours.
Tokens parked in browser storage, where any script injection reads and exfiltrates them, when cookies marked unreadable by script exist precisely so that cannot happen. Tokens with no expiry at all, immortal credentials that then back up into your log aggregator forever. And weak shared secrets, where a dictionary word falls to an offline cracker at millions of guesses a second against a token the attacker already holds.
Not one of those is a cryptography failure. The algorithms have not been broken. Every item is a check somebody skipped. That is why use a maintained library and pin the algorithm beats any exotic hardening you could invent.
Worked example
Tim McLean's March 2015 disclosure, published with Auth0, walked through the attack against then-current libraries including pyjwt and php-jwt. The steps fit in a tweet: take any valid token from the target, base64-decode the middle segment, change sub from your ID to the admin's, rewrite the header to {"alg":"none"}, re-encode, and delete everything after the second dot. Vulnerable libraries returned verified on that string, because the spec technically permits unsigned tokens and the code obligingly honored the header's choice. Patches landed within days, and the fix shaped every modern API: jsonwebtoken, pyjwt, and friends now require callers to pass an explicit algorithms allowlist, and refuse none unless you opt in loudly. Ten years later, algorithm confusion still reappears in new libraries often enough that it stays in every pentester's first-hour checklist.