Broken object-level authorisation: the API bug behind most data leaks
BOLA tops the OWASP API Security Top 10. How it happens in a typical NestJS API, how testers find it, and the pattern that prevents it.
Broken object-level authorisation (BOLA) is number one on the OWASP API Security Top 10 (2023). It does not need clever exploitation. An attacker changes an ID in a request and gets someone else’s data.
What it looks like
Here is a handler for fetching a loan application. It checks that the caller is logged in. It does not check that the application belongs to them.
@UseGuards(AuthGuard)
@Get("applications/:id")
async getApplication(@Param("id") id: string) {
// Any logged-in user can read any application by guessing or iterating IDs.
return this.applications.findById(id);
}
Authentication answers who are you? Authorisation answers are you allowed to touch this specific record? BOLA is what happens when only the first question is asked.
How testers find it
The method is simple and repeatable, which is why it is so often found:
- Create two accounts, A and B.
- As A, perform every action that references an object: view, edit, delete, download, share.
- Replay each request with A’s object ID but B’s session token.
- Any request that succeeds is a finding.
Sequential or predictable IDs make it worse, but switching to UUIDs is not a fix. IDs leak through URLs, logs, shared links and other API responses.
The fix: scope every query to the caller
Make ownership part of the query rather than a check someone might forget.
@UseGuards(AuthGuard)
@Get("applications/:id")
async getApplication(@Param("id") id: string, @CurrentUser() user: User) {
const application = await this.applications.findOne({
where: { id, ownerId: user.id },
});
if (!application) throw new NotFoundException();
return application;
}
Two details matter here:
- Return 404, not 403. A 403 confirms the record exists, which tells an attacker their ID guess was right.
- Push it into one place. A repository method like
findOwnedBy(user, id)makes the safe path the easy path. For multi-tenant systems, row-level security in Postgres enforces the same rule even when application code gets it wrong.
Test for it continuously
Write the two-account test as an automated integration test for every endpoint that takes an object ID. It is cheap, and it catches the regression the next time someone adds an endpoint in a hurry.
- api-security
- owasp
- nestjs
- authorisation
Related service
Web, API & mobile penetration test
OWASP-based testing of web apps, APIs and mobile apps, with a retest included.