Testing Authorization Coverage in Large SaaS Codebases

A systematic method for proving authorization works across every endpoint and tenant boundary.

· 9 min read
Cover illustration for “Testing Authorization Coverage in Large SaaS Codebases”
Access Control · November 4, 2026 · 9 min read · 1,926 words

Testing authorization coverage in a large SaaS codebase means proving that users can reach only the objects and actions their permissions allow. That proof requires more than a scanner or a few cross-tenant requests. Because authorization is scattered across the application, the team must map every permission boundary before testing, then record what happened at each one.

Why authorization failures dominate SaaS breach reports while other vulnerability classes shrink

Multi-tenant SaaS separates customer data logically. The wall between Tenant A and Tenant B depends on application-layer authorization, so a missing check can expose another customer’s data even when the user has logged in correctly. Authentication establishes only who the caller is.

Broken object-level authorization, insecure direct object references, and misconfigured tenant isolation are among the most common findings in SaaS penetration tests. These failures share a basic defect. The application accepts an object reference without fully checking the caller’s authority over it. A valid session and a valid object identifier can still give a caller access they should not have.

That explains the structural risk, but it does not establish that authorization bugs cause most SaaS breaches or that every other vulnerability class is shrinking. Identity compromise is broader than broken authorization, and breach reports should not blur that distinction. Authorization checks are spread across every endpoint, every role, and every tenant, so the boundary can fail in many places.

How authorization logic gets distributed across a large codebase and why that distribution hides gaps

Authorization is not a single module in a large application. Each endpoint, background job, webhook receiver, and inter-service call asserts some part of the policy, often inconsistently. For example, the UI may hide admin buttons from standard users even though the API behind it accepts their authenticated requests. The hidden button is not an access check.

The same split can occur after a request leaves the API. A background job may process a tenant-scoped record without re-checking the caller’s tenant, while a webhook receiver may trust an externally supplied tenant ID. Shared caches, queues, job processors, and file storage add more paths across the boundaries between customers. OAuth app installations, outbound webhooks, and SaaS-to-SaaS connections extend those paths beyond code the team directly controls.

OWASP guidance says each endpoint acting on a client-supplied object identifier should check whether the current user may perform that action on that object. In shared SaaS, the decision depends on object ownership, tenant membership, role within the tenant, and workflow state. All four dimensions must hold at once. Checking role alone says nothing about whether the object belongs to the right customer.

A denied read may reveal a filename, count, timestamp, search hit, or notification, while a denied write may still enqueue a job or update a secondary system. The HTTP status is only part of the result. Checking a few visible endpoints cannot show whether these less visible paths follow the same rules.

Why automated scanning and point-in-time blackbox tests leave authorization gaps intact

The standard scanning tools address a different problem. SAST can find hardcoded secrets, SQL injection patterns, template-level XSS, weak cryptography, and insecure deserialization. Dependency auditors and automated scanners can quickly identify known CVEs, outdated libraries, and misconfigurations. Those strengths do not give them the runtime database state and session context needed to decide whether a particular user may access another tenant’s record.

Blackbox testing has a related visibility limit. When the UI hides admin controls, a tester who relies on that interface may never find the admin endpoints that accept standard-user tokens. A point-in-time test also says little about paths outside its agreed scope. A contract that says only “test authentication” does not specify JWT validation across every endpoint, OAuth refresh logic, or cross-tenant privilege escalation.

Greybox testing is the minimum viable approach here. Testers need accounts in separate tenants and at different role levels so they can work through tests for API manipulation, parameter tampering, session substitution, and shared-resource abuse. Without those accounts, they lack the contrasting identities needed to test the boundary.

Code access exposes routes the UI never links to and helps identify authorization decisions inside workers and service calls. To find these gaps, testers need to map the permissions before they send requests.

Building the permission map before testing begins

The practical method starts by defining what “tenant” means in the product. It might be an organization, workspace, subsidiary, project, healthcare customer, or reseller account. Some users may belong to more than one tenant, and global support roles may enter a customer’s context. Testers need to know these relationships before they can decide which transitions are allowed.

Discovery must then cover the full application surface. That includes REST endpoints, GraphQL APIs, internal microservice APIs, webhook receivers, WebSocket and SSE connections, admin consoles, background workers, scheduled jobs, message queues, and third-party OAuth integrations. The UI is not the asset inventory. Code-aware discovery can extract routes, authentication requirements, and data flows faster than manual enumeration and expose paths that have no visible control.

For each endpoint using a client-supplied object identifier, record the object type, allowed actions and roles, owning tenant, and workflow states that gate access. An illustrative entry for an object-read endpoint would name the object, its tenant scope, the permitted roles, and any workflow restriction. This is a practical test specification. It states the decision the application is supposed to make.

Account provisioning must match that specification. Testers need separate tenants and all relevant roles, including standard users, admins, cross-tenant support, and API-key-only service accounts. Privileged pathways also need explicit entries, including inherited roles, API tokens, admin groups, and revocation gaps. Otherwise, the map describes tests the team cannot actually execute.

Executing tenant-isolation tests systematically across the permission map

Execution should traverse the mapped object-action-role combinations across tenant boundaries. Before it begins, agree on controlled records in each tenant so that probes target known fixtures. Then authenticate as Tenant B’s user and substitute Tenant A’s UUID in a request. Check the returned object, partial signals, and error response, because a rejected request alone does not prove isolation.

Apply that pattern across the input locations. Substitute UUIDs, integers, and slugs in REST and GraphQL requests, then modify tenant identifiers in query parameters, request bodies, and HTTP headers. Use valid session tokens from one tenant against resources scoped to another. These are separate checks because the application may enforce scope differently along each path.

Test S3 keys and blob paths for tenant-scoped file access, and supply cross-tenant object identifiers in inbound webhook payloads to check ownership validation. Probe shared caches, queues, and background processors for leakage. A rejected write still needs to be checked for downstream effects, including a job that was enqueued or an update to a secondary system.

Test role escalation on its own within the correct tenant. Standard users should not be able to take admin actions just because their tenant matches the object’s tenant. Cover JWT signature validation and algorithm confusion across every endpoint, plus OAuth refresh token rotation and revocation on logout. Checks of business logic should also cover subscription tier bypass, pricing manipulation through billing APIs, and direct API access that gets around hidden feature gates.

Every finding needs a working, reproducible exploit. A response revealing another tenant’s count or timestamp turns a “possible” disclosure into a confirmed authorization failure. The evidence must show the request and the boundary it crossed.

Tracking coverage with an endpoint-and-workflow matrix

The permission map states what should be allowed. The coverage matrix records what was tested and what happened, making it possible to distinguish systematic testing from a collection of interesting findings. Without that record, completeness is only an assertion. A report that finds no failures may still leave large parts of the application untested.

Rows should represent endpoints and privileged workflows. Columns should represent role-tenant combinations, including Tenant A standard user, Tenant A admin, Tenant B standard user, cross-tenant support role, and service account. Each cell shows whether the authorization check ran and what happened.

Engineers and auditors use the same record differently. Engineers need to locate confirmed failures and follow their remediation, while auditors need evidence that the test covered the full permission map. Blank rows show gaps in coverage that must be dealt with before the test ends and kept in view when the team draws its conclusion.

Retest evidence belongs in the same matrix. After a fix ships, re-execute the request that demonstrated the failure and record the new result in the affected cell. A code change is not a retest. Until that cell reflects the repeated test, the matrix cannot show that the original attack path has been closed.

Integrating authorization testing into the development lifecycle so gaps do not re-open

A completed matrix is a snapshot of the application that was tested. Its coverage starts expiring when the next pull request changes a permission boundary. New endpoints, background jobs, and integration points create untested paths, so a team shipping multiple times per week cannot rely on an annual engagement alone. The initial matrix must become a maintained baseline.

To keep that baseline useful, review pull requests for authorization-relevant changes. New route definitions, tenant-scoping middleware changes, modified role checks, and new object types accepting client-supplied identifiers all signal a need for coverage. Each new endpoint starts as an uncovered row. The team needs to test that row before the next compliance window closes.

SAST can help identify these changes without proving their safety. It can flag patterns such as missing ownership checks, tenant identifiers not validated against the session, or inconsistent role checks across similar endpoints. This does not contradict its runtime limits. A code-level warning identifies a place to investigate, while boundary testing establishes what the application actually permits.

A fix that passes code review remains unverified until the original exploit request has been repeated. On-demand retesting by the team that found the issue keeps that work tied to the actual attack path. A shared communication channel, such as Slack, also lets engineers ask whether a proposed architectural change introduces a new authorization surface before it ships.

What a credible authorization test report looks like and what to demand from the team delivering it

Demand a coverage matrix, reproducible exploit requests, and attack chains showing how cross-tenant boundaries were crossed. Where a small disclosure led to access to another tenant’s object, the report should document that progression. Labels such as “potential” or “possible” are not substitutes for tested findings.

Remediation advice must also fit the code under test. Generic recommendations and scanner-shaped output with a cover page do not demonstrate manual authorization testing. The same scrutiny belongs in the pre-engagement conversation. A team should be able to explain object-level authorization testing across microservices and describe a specific tenant-isolation failure and how it could be exploited, rather than deferring every technical question until a consultant is assigned.

Credentials provide a limited but useful signal. CISSP and the standard CEH knowledge exam do not require hands-on exploitation, though the optional CEH Practical add-on does. OSCP and equivalent offensive certifications demonstrate practical exploitation ability. Pricing deserves scrutiny too, because manual testing across a role-tenant matrix does not scale linearly by IP address or page count.

Finally, demand an explanation of how the team will enumerate the full permission map. Whitebox source access exposes unlinked endpoints, background jobs that skip tenant checks, and middleware whose intended policy differs from its behavior. Without that visibility, claims of complete coverage need careful qualification. The deliverable should make every tested boundary, uncovered gap, proven failure, and retest result visible enough for the engineering team to act on.

Filed underAccess Control

More in Access Control