Audit BenchAi
← All posts

Reviewing JWT and Session Authentication for Common Mistakes

·7 min read

Authentication — proving who someone is — is a different problem from authorization, which is deciding what they're allowed to do once you know. Broken access control, the authorization failure, gets a lot of attention because it's common and usually a simple missing check. Authentication has its own recurring mistake patterns, and because so many of today's applications hang their entire session model on a JSON Web Token, those mistakes tend to look the same across otherwise unrelated codebases.

The "alg: none" and algorithm-confusion mistakes

A JWT carries its signing algorithm in its own header, and some JWT libraries — especially older ones — will trust whatever algorithm the token claims rather than the one the server actually expects. Historically this allowed an attacker to submit a token with "alg": "none" and have it accepted with no signature at all. A closely related mistake is algorithm confusion: a server configured to verify tokens with an asymmetric algorithm like RS256, using a public key, that can also be tricked into accepting a token signed with HS256 using that same public key as if it were a shared HMAC secret. Both are library-configuration issues, and both are worth confirming explicitly rather than assuming the library's defaults are safe.

Signature verification that isn't actually happening

Most JWT libraries expose a decode function and a separate verifyfunction — decode reads the payload without checking the signature at all, verify does both. It is an easy mistake to call decode because it's convenient for reading a claim, and never realize the signature was never checked. Any code path that trusts a claim from a decoded-but-unverified token is equivalent to trusting whatever the client sends, no matter how legitimate the rest of the authentication flow looks.

Token lifecycle mistakes

  • No expiration claim set, or an expiration far longer than is reasonable for what the token grants access to.
  • No way to revoke a token before it naturally expires — a purely stateless JWT setup has no answer for "log this user out everywhere" or "invalidate every token issued before this password change."
  • Refresh tokens that can be reused indefinitely rather than rotated on each use, with reuse of an old refresh token treated as a sign of theft.
  • Sensitive claims (role, permission level) embedded in the token and trusted long after they might have changed server-side.

Session-cookie mistakes for teams not using JWTs

Traditional server-side sessions have their own well-known failure modes worth checking in review: session fixation, where a session identifier issued before login is kept after login instead of being rotated, letting an attacker who set that ID in advance hijack the authenticated session; missing Secure, HttpOnly, or SameSite cookie flags on the session cookie; and session identifiers generated with anything less than a cryptographically secure random source, making them guessable.

Checklist for reviewing authentication code

  1. The JWT library is configured to accept only the specific algorithm the server expects, not whatever the incoming token claims.
  2. Every code path that reads a claim from a token has gone through actual signature verification, not just a decode call.
  3. Tokens have a reasonable expiration, and there is a real mechanism to invalidate them early when needed.
  4. Refresh tokens rotate on use, with reuse of a stale token treated as a signal, not ignored.
  5. Session identifiers are rotated after login and carry Secure, HttpOnly, and an explicit SameSite setting.

Read about reviewing broken access control before it ships →

See how to keep secrets and API keys out of source code →