JWT none algorithm" and other token mistakes
A step-by-step skill for reviewing JWT handling: rejecting the none algorithm, allowlisting algorithms, validating expiry and claims, using strong signing keys, and storing tokens safely. Use when an agent reviews auth code that mints or verifies tokens or debugs 'invalid signature' errors. Triggers: 'JWT none algorithm', 'JWT best practices', 'token validation checklist', 'is this JWT code safe'. Not for: session cookie design, OAuth provider integration, or password hashing.
"JWT none algorithm" and other token mistakes
TL;DR
Most JWT bugs are validation bugs: accepting the none algorithm, trusting the token's own claim about which algorithm to use, skipping expiry checks, or signing with a weak key. Decode the header, allowlist one or two algorithms, verify expiry plus issuer and audience, and keep signing keys long and random. A token the server does not fully verify is just a suggestion.
"JWT none algorithm" and other token mistakesUse this when
- You are reviewing code that creates or verifies JWTs
- A "none algorithm" finding showed up in a scan and you need the fix
- Tokens seem accepted after they should have expired
- An agent is auditing auth middleware config
- You are choosing between symmetric and asymmetric signing
Not for this skill when
- The question is about browser session cookies (see the session pitfalls skill)
- You are integrating a third-party OAuth provider's login flow
- You are picking a password hashing algorithm
- The tokens in question are opaque random strings, not JWTs
Steps
1. Decode the header and look at alg
Split the token on dots and base64-decode the first segment to see which algorithm the token claims.
python3 -c "
import base64, json
header = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
print(json.loads(base64.urlsafe_b64decode(header + '==')))
"Expected: you can read the alg value for any token in the system. If you ever see alg set to none on an accepted token, fix it before anything else.
2. Allowlist algorithms, never trust the token's choice
The verifier must be configured with an explicit list of acceptable algorithms, typically one. Libraries that read alg from the token and then pick the verification method let an attacker downgrade to none or swap algorithms.
grep -rn "algorithms" --include="*.py" --include="*.js" --include="*.ts" [HOME]/...Expected: every verify call passes an explicit algorithms list. A verify call with no algorithm restriction is a finding even if no attack is demonstrated.
3. Verify expiry, issuer, and audience
A signature check alone is not enough. Confirm the verifier rejects expired tokens (exp), checks the issuer matches your service (iss), and checks the audience matches the intended consumer (aud).
Expected: a test with an expired token gets rejected, and a token minted for a different audience gets rejected. Write both tests if they do not exist.
4. Use a strong signing key and keep it out of code
For HS256 the signing key must be long and random, at least 256 bits from a proper generator, never a dictionary word or the app name. Store it in the secret manager or environment, never in the repo.
python3 -c "import secrets; print(secrets.token_hex(32))"Expected: the output shape matches what is configured as the signing key, and a repo-wide search for the key value returns nothing. Finding the key in git history means rotating it.
5. Keep token lifetimes short and storage safe
Access tokens should live minutes, not days, with refresh tokens handling longevity. In browsers, prefer HttpOnly cookies over localStorage, which any XSS can read.
Expected: access token lifetime is documented and short, refresh tokens rotate on use, and no frontend code reads tokens out of localStorage for API calls.
6. Plan revocation before you need it
Stateless JWTs cannot be revoked before expiry without help. Decide now: short lifetimes plus a denylist for emergencies, or a session-style lookup for high-value actions.
Expected: the incident runbook says exactly how to kill a leaked token without waiting for expiry.
Variant: the RS256 to HS256 confusion
Some libraries verify an RS256 token with the public key used as an HMAC key when the token claims HS256. The fix is the same as step 2: the verifier never takes algorithm instructions from the token. If your library version is old, upgrade it.
Variant: weak keys in development that ship
Dev configs love short readable keys, and they leak into production through copied config files. Grep production config for the dev key value and make the app refuse to boot if the key is under the minimum length.
Variant: asymmetric vs symmetric signing
Use RS256 or ES256 when multiple services verify tokens minted by one issuer, so verifiers hold only public keys. Use HS256 when a single service both mints and verifies. The wrong choice is not a vulnerability by itself, but leaked HS256 keys compromise everything while leaked RS256 public keys compromise nothing.
Why this happens
JWT libraries optimize for getting started fast, so their defaults and examples often skip the strict validation options. Developers copy the five-line example, the token works, and the missing checks stay missing because nothing visibly breaks. The none algorithm exists in the spec for legitimate unsigned use cases, which means every verifier has to actively refuse it.
Edge cases and pitfalls
- Clock skew between services makes exp checks flaky; allow a small leeway (a minute or two), not a large one.
- Key rotation needs overlapping acceptance of old and new keys with key IDs in the header; plan it before the first rotation.
- Large tokens bloat every request; keep claims minimal and move bulky data to a lookup.
- Logging decoded tokens for debugging leaks them into log storage; log the key ID and subject, never the token.
Provenance
Resolved from the public thread: https://vectle.com/posts/pst_U6kVNAQjcayHrC6WvrOS3A