Lesson 3 of 3 in Broken Authentication
Verify What You Issued
The fix is to stop letting the token choose how it is checked, and to stop trusting the claims it carries about privilege. Pin the algorithm, and read authority from the server, not from the cookie.
2 min read
Not yet reviewed
So I just refuse tokens with alg:none?
That closes this door and leaves the frame. The real fix is to stop asking the token how to check itself at all — you issued it, so you already know.
Pin the algorithm
The server issued the token, so the server knows how it was signed. It should verify with that algorithm and no other — never with the one the token's own header names. A verifier that reads alg from the token is taking instructions from the thing it is meant to be checking.
Letting the token choose (the bug)
const alg = header.alg
return verifyWith(alg, token)Pinning the algorithm (the fix)
return verifyWith('HS256', token) // never header.algThe fix hard-codes the one algorithm the server signs with. none is not on the menu, and neither is an attacker downgrading an RS256 token to be "verified" with the public key as an HMAC secret — the other classic version of this bug.
Do not trust claims about privilege
The second half of the forgery worked because the app read role from the token. A token can safely carry *identity* — who you are — because that is what the signature attests. It should not carry *authority* the server then trusts blindly. Re-read the role from the database for the user the token names, so a forged or stale claim buys nothing.
The non-fixes
Two habits look like fixes here and are not, in the now-familiar way — they patch a symptom and leave the mechanism.
block "alg":"none" the RS256->HMAC downgrade still gets you in
put the role in the token signed, but still authority the client carries
- Line 1Refusing the literal string
nonestops this exact payload and not the family. Pinning the algorithm stops all of them, because the server stops consulting the header at all. - Line 2A signed role claim is not tamper-*proof* the moment the signature can be bypassed — and even when it cannot, it goes stale: revoke someone's access and their old token still says they have it. Authority belongs server-side.
Signed tokens are everywhere and mostly fine. What is the one-line rule that separates the safe uses from this lab?