BIPI
BIPI

Six API Authentication Bypasses We Find Repeatedly in Pentests.

Cybersecurity

API auth code looks fine in review and breaks under attacker pressure. Here are the six patterns we find on nearly every engagement, why they survive code review, and what to test for.

By Arjun Raghavan, Security & Systems Lead, BIPI · May 5, 2026 · 8 min read

#api-security#penetration-testing#authentication#owasp

Of the API pentests we ran in the last year, roughly two-thirds had an authentication or authorisation bypass that let us reach data we should not have reached. None of them had no auth at all. Every one of them passed code review. The bugs are not exotic. They are the same six patterns, year after year, in different disguises.

1. The optional-auth middleware

The pattern: a middleware sets req.user if a valid JWT is present, but does not reject the request if the token is missing or invalid. Downstream handlers then check req.user.role === 'admin' but never check req.user existence. With no token, req.user is undefined, the role check is undefined === 'admin' which is false, and the handler proceeds to whatever the default behaviour is. Sometimes that default is 'public read.' Sometimes it is 'serve everything.'

Why it survives review: the middleware code reads correctly. The handler code reads correctly. The interaction is the bug. Pentest test: hit every endpoint with no auth header and look at what comes back.

2. JWT signature verification disabled in tests, shipped to prod

Someone added a flag for unit tests that lets them mint tokens without the signing key. The flag was supposed to be off in production. The default is off in production. But the env var got set somewhere in staging, the staging build artifact got promoted to production, and now you have a JWT verification step that accepts the alg: none header.

Why it survives review: the flag was added 18 months ago by someone who has since left. Nobody currently on the team knows it exists. Pentest test: send a JWT with alg: none and see if it is accepted.

3. IDOR through guessable IDs in deeply-nested routes

/api/v1/orgs/123/projects/456/documents/789. The auth check verifies the requesting user belongs to org 123. It does not verify that project 456 belongs to org 123. So a user from org 123 can read documents from project 999 (which belongs to org 555) by guessing the project ID.

Why it survives review: the route looks scoped because it has the org ID in the URL. The controller author assumed the routing layer enforced the relationship. The routing layer did not. Pentest test: enumerate IDs at every level and see what you reach.

Most IDOR bugs in 2026 are not at the leaf. They are at the second-to-last node, where developers stop checking ownership.

4. Mass assignment on profile-update endpoints

PUT /api/v1/users/me accepts a JSON body and merges it into the user record. The intended fields are name, email, avatar. But the JSON body parser does not filter, and the database update does not whitelist, so a request with { 'is_admin': true, 'organization_id': 'other-org-uuid' } will silently update those fields too.

Why it survives review: the API documentation lists name, email, avatar. The reviewer reads the doc and assumes the implementation matches. Pentest test: include is_admin, role, owner_id, organization_id, balance, and other privileged fields in update requests and see if any stick.

5. CORS misconfiguration that allows credentialed cross-origin requests

Access-Control-Allow-Origin is set to the request's Origin header (echoing it back), and Access-Control-Allow-Credentials is true. Any malicious site can now make authenticated requests to the API with the user's cookies.

Why it survives review: CORS feels like a frontend concern, so backend reviewers skim it. The dev tested with curl, which does not enforce CORS. Pentest test: create a hosted page on a different origin that fetch()-es the API with credentials: 'include' and see what comes back.

6. Token reuse across tenants in a multi-tenant SaaS

User signs into tenant A. The JWT includes user_id but not tenant_id. The user can take that token and call /api/v1/tenants/B/data, and as long as user_id is also a member of tenant B (perhaps a customer of both), the request succeeds even though the user was logged into tenant A. Cross-tenant data leakage is a recurring SaaS bug.

Why it survives review: the token-issuance code looks fine. The data-access code looks fine. The bug is that the token does not bind to a tenant, and the access path does not require it to. Pentest test: log into one tenant, copy the JWT, hit endpoints on a different tenant the user has any access to.

What the fix looks like

  • Fail closed in middleware: if no valid token, return 401 immediately. Never let a request reach a handler with req.user unset.
  • Strip JWT verification flags from production builds entirely. Make alg: none impossible at the library level, not the config level.
  • Authorisation checks at every level of nesting. If your route is /a/:x/b/:y/c/:z, you need three ownership checks, not one.
  • Whitelist updatable fields explicitly. Never spread untrusted JSON into an ORM update.
  • CORS allowlist of specific origins. Echoing back is never correct for authenticated APIs.
  • Bind tokens to tenant IDs. Validate tenant scope on every request, not just at login.

Closing

These bugs are not sophisticated. They are the result of code review focused on individual files and pull requests, where the bug exists in the interaction between files. The defence is a different kind of test: scripted enumeration that exercises every endpoint with every plausibly-malicious payload. Bug bounties find these eventually, but the cheaper path is to test for them yourself before shipping.

Read more field notes, explore our services, or get in touch at info@bipi.in. Privacy Policy · Terms.