Authentication answers "who are you?" and authorization answers "what are you allowed to do?" Mixing them up, or checking only one, is behind a large share of serious application breaches. Broken access control was the number one category in the OWASP Top 10 2021, which makes it the place to spend your review time.

Why are the two so often confused?

A user being logged in proves identity, not permission. A common bug is an endpoint that checks for a valid session and then trusts whatever object ID the request supplies. The code works in testing because testers only ever request their own data.

What to look for in authorization code

  • Missing function-level checks. Admin and internal endpoints that are hidden from the menu but not protected on the server.
  • Insecure direct object references (IDOR). A request like GET /invoices/1042 that returns the invoice without checking that it belongs to the caller.
  • Client-side enforcement. Role checks that exist only in front-end code. Anything the browser enforces, an attacker can skip.
  • Default-allow logic. Code that grants access unless a rule denies it. Safe code denies unless a rule allows.
  • Mass assignment. Binding request bodies straight onto models so a user can set fields such as role or is_admin.
// Risky: trusts the ID, never checks ownership
app.get('/invoices/:id', requireLogin, async (req, res) => {
  res.json(await Invoice.findById(req.params.id));
});

// Better: scope the query to the caller
app.get('/invoices/:id', requireLogin, async (req, res) => {
  const inv = await Invoice.findOne({ _id: req.params.id, ownerId: req.user.id });
  if (!inv) return res.sendStatus(404);
  res.json(inv);
});

What to look for in authentication code

  • Password storage. Slow, salted, purpose-built hashing such as bcrypt, scrypt or Argon2.
  • Session handling. New session identifiers after login, secure random generation, sensible expiry, and invalidation on logout and password change.
  • Token validation. For JWTs, verify the signature, the algorithm, the issuer, the audience and the expiry. Reject the none algorithm.
  • Brute-force protection. Rate limiting or lockout on login, reset and verification-code endpoints.
  • Account recovery. Reset tokens that are random, single-use and short-lived. Recovery flows are often the weakest door in the building.

How can you make these bugs less likely?

Centralize authorization in one layer, such as middleware, policies or a dedicated service, instead of scattering checks through handlers. When every access decision goes through the same code, a reviewer only has to verify that the route uses it. Add tests that log in as one user and try to reach another user's data. If your team wants an independent look at login, session and permission logic, that is a core part of a secure code review engagement.

Related reading: Reviewing Cryptography Code: Common Mistakes to Catch and Finding Business Logic Flaws in Code Review.

Key takeaways

  • Being logged in is not the same as being allowed. Check ownership and role on every object access, on the server.
  • Deny by default, and centralize authorization so it can be reviewed in one place.
  • Verify tokens fully and protect login and recovery flows from brute force.
  • Test with two users to prove one cannot reach the other's data.