Broken Access Control in SaaS Platforms
How broken access control emerges in SaaS architectures and how to enforce authorization boundaries across tenants, roles, and resources.
Broken Access Control in SaaS Platforms
Use this with Secure API Authentication vs Authorization, What Is BOLA and Why It Breaks SaaS APIs, and RBAC Design in SaaS Applications for a full authorization hardening model.
For practical broken access control testing across APIs, use Agnite Scan.
When those checks are spread across controllers, services, and data access code, a SaaS security audit helps verify that the enforcement still holds under real object and tenant permutations.
If you’re building a SaaS product, this is the point where authorization stops being a local code check and becomes a system boundary. Teams that need to build a system like this usually design access control with the rest of the platform.
Broken access control is the most common critical vulnerability in modern SaaS systems. It appears repeatedly in public breach disclosures and security reports because authorization is not a single mechanism. It is an architectural property of the entire system.
Authentication determines who the user is. Access control determines what the user is allowed to do.
In SaaS platforms authorization must operate across several dimensions:
- tenant isolation
- user identity
- organization roles
- resource ownership
- service boundaries
If authorization logic is not designed as a system-wide architectural layer, small implementation mistakes accumulate until privilege boundaries collapse.
This article examines how broken access control emerges inside SaaS architectures and how engineering design must prevent it.
Problem Definition and System Boundary
A SaaS platform typically exposes a public API surface structured like this:
Browser
↓
API Gateway / Load Balancer
↓
Application Services
↓
Database
Within that request path several security assumptions must hold simultaneously.
A request must prove:
- the identity of the user
- the organization or tenant the user belongs to
- the roles and permissions granted to that user
- ownership of the resource being accessed
If any of these checks is missing or implemented inconsistently, the system becomes vulnerable to broken access control.
Authorization logic is often scattered across:
- API controllers
- service layer logic
- database queries
- background jobs
- internal service APIs
Fragmentation increases the probability of inconsistent enforcement.
Example:
GET /api/projects/8472
The endpoint may verify authentication but fail to verify whether the project belongs to the user’s tenant.
That missing constraint becomes a cross-tenant data exposure.
Why Broken Access Control Appears in SaaS Systems
Access control failures rarely originate from a single bug. They usually emerge from architectural shortcuts.
Implicit Authorization Assumptions
Developers assume upstream layers already performed checks.
Example assumption:
Controller verified tenant access.
A service method later performs an unrestricted query.
var project = await db.Projects.FindAsync(projectId);If another endpoint calls the same service, the authorization guarantee disappears.
Authorization must never depend on fragile assumptions between layers.
Resource Identifier Exposure
APIs frequently expose numeric or GUID identifiers.
Example:
GET /api/invoices/51273
If the system checks authentication but not ownership, attackers can enumerate identifiers and access resources belonging to other tenants.
This vulnerability class is often called IDOR.
In SaaS environments this becomes a tenant isolation failure.
Authorization Outside the Data Layer
Another common mistake is performing authorization checks after loading the resource.
Example:
if (invoice.OrganizationId != user.OrganizationId)
{
throw new UnauthorizedException();
}
return invoice;Problems with this pattern:
- unauthorized data is loaded first
- checks may be forgotten
- duplication occurs across endpoints
Authorization constraints should be embedded in queries whenever possible.
Architectural Patterns for Strong Access Control
Reliable access control emerges when authorization constraints are embedded directly into architecture.
Tenant-Scoped Data Access
Tenant isolation forms the first security boundary.
Example EF Core configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasQueryFilter(p => p.OrganizationId == _tenantContext.OrganizationId);
}Global query filters automatically enforce tenant isolation.
Query flow:
Request → TenantContext → EF Core Query Filter → Database
Developers no longer need to remember tenant checks manually.
Resource Ownership Validation
Tenant isolation alone is insufficient.
Users inside the same tenant may have different permissions.
Example roles:
- organization admin
- team member
- billing manager
- viewer
ASP.NET Core policy example:
services.AddAuthorization(options =>
{
options.AddPolicy("ProjectWrite", policy =>
policy.RequireClaim("role", "admin", "editor"));
});Endpoint:
[Authorize(Policy = "ProjectWrite")]
[HttpPost("/projects/{id}")]
public async Task<IActionResult> UpdateProject(Guid id)
{
...
}Policies centralize authorization logic.
Ownership-Based Access Checks
Some resources require additional ownership validation.
Example query:
var project = await db.Projects
.Where(p => p.Id == projectId &&
p.OrganizationId == tenantId &&
p.OwnerUserId == currentUserId)
.FirstOrDefaultAsync();The authorization constraint becomes part of the query.
Unauthorized resources cannot be loaded.
Service-Level Authorization Guards
Large SaaS systems introduce internal service layers.
Authorization must exist there as well.
Example:
public async Task<Project> GetProjectForUser(Guid projectId, UserContext user)
{
return await db.Projects
.Where(p => p.Id == projectId &&
p.OrganizationId == user.OrganizationId)
.FirstOrDefaultAsync();
}Controllers call authorization-aware services rather than raw repositories.
Implementation Example in ASP.NET Core
A typical SaaS authorization architecture includes multiple layers.
Identity Layer
Responsible for authentication and token issuance.
Example token payload:
{
"sub": "user_24817",
"org": "org_912",
"roles": ["admin"]
}Tenant Context Middleware
Extracts tenant identity from the request.
public class TenantContextMiddleware
{
private readonly RequestDelegate _next;
public async Task Invoke(HttpContext context, TenantContext tenant)
{
tenant.OrganizationId = context.User.FindFirst("org")?.Value;
await _next(context);
}
}Authorization Policies
Example policy:
options.AddPolicy("BillingAccess", policy =>
{
policy.RequireAssertion(ctx =>
ctx.User.HasClaim("role", "admin") ||
ctx.User.HasClaim("role", "billing"));
});Data Layer Enforcement
Tenant filtering must exist in database queries.
var invoices = await db.Invoices
.Where(i => i.OrganizationId == tenantId)
.ToListAsync();Even if API logic fails, the data layer still enforces isolation.
This layered design reduces catastrophic mistakes.
Real Failure Scenario
Consider a SaaS billing platform.
Invoices table:
Invoices Id OrganizationId Amount Status CreatedAt
Endpoint:
GET /api/invoices/{id}
Initial implementation:
[Authorize]
public async Task<IActionResult> GetInvoice(Guid id)
{
var invoice = await db.Invoices.FindAsync(id);
return Ok(invoice);
}The endpoint checks authentication but not tenant ownership.
Attack scenario:
- attacker logs in
- attacker requests
/api/invoices/1001 - attacker increments identifiers
- attacker retrieves invoices from other tenants
The fix embeds tenant filtering in the query.
var invoice = await db.Invoices
.Where(i => i.Id == id &&
i.OrganizationId == tenantId)
.FirstOrDefaultAsync();Even if arbitrary IDs are provided, unauthorized data cannot be returned.
Operational Considerations
Security Testing
Recommended tests:
- ID enumeration testing
- cross-tenant API fuzzing
- privilege escalation attempts
- automated authorization tests
Example test:
Tenant A user attempts to access Tenant B resource → expect 403 or 404.
Observability and Audit Logging
Access violations should be observable.
Example audit event:
EventType: AuthorizationFailure UserId: user_24817 Resource: Invoice ResourceId: 51273 OrganizationId: org_912 Timestamp: …
Security monitoring systems can detect enumeration attempts or privilege escalation patterns.
Authorization Code Reviews
Code review checklist:
- resource queries include tenant constraints
- ownership rules enforced
- authorization policies defined
- service layer does not bypass checks
Broken access control often enters through small overlooked shortcuts.
These gaps usually stay hidden until the same permissions are exercised across multiple tenant and object combinations.
Validate broken access control before release
We review tenant boundaries, ownership checks, and authorization paths across the API and data layer. The goal is to catch object-level access failures while they are still reproducible in testing.
Linking Back to the Pillar
Broken access control is not a single bug class. It represents a failure of architectural boundaries.
Secure SaaS systems treat authorization as a system-wide constraint embedded in:
- identity infrastructure
- tenant context propagation
- service layer contracts
- database queries
When these layers cooperate, access control remains resilient even if individual endpoints contain mistakes.
If authorization now spans controllers, services, caches, and data access, this is the point where a SaaS security audit is usually needed to verify the boundary under real request conditions.
If you need to validate the full chain in a live system, a broken access control audit checks ownership rules, tenant boundaries, and data-layer enforcement together.
For a broader architectural perspective, see SaaS Security Architecture: A Practical Engineering Guide.
Need implementation support? Review the API security testing tool or explore our services.
Related Articles
Continue reading in SaaS Security
Building SaaS with complex authorization?
Move from theory to request-level validation and architecture decisions that hold under scale.
SaaS Security Cluster
This article is part of our SaaS Security Architecture series.
Start with the pillar article: SaaS Security Architecture: A Practical Engineering Guide
