Chapter 2 of 7 · 5 checks · 2 min
Object, property and function level authorization
Checks that a logged-in user can only reach their own objects, fields and functions (OWASP API1, API3 and API5:2023).
Every request for an object checks the caller may access that object (BOLA)Must
Why it matters and how to check
Broken object level authorization is the top risk on the OWASP list. The API checks that you are logged in, then loads
/orders/1043without checking that order 1043 is yours.To verify: create two test users, A and B. As A, create an order, file or message and note its ID. As B, request, update and delete that ID on every endpoint that takes it. Every attempt must return
403or404. In code, the lookup should filter by owner (or check a policy) in the same query.Example
TypeScript // scope the query to the caller, not just the id const order = await db.order.findFirst({ where: { id: params.id, userId: session.userId }, }); if (!order) return new Response('Not found', { status: 404 });Unguessable IDs are used, but never as the only protectionShould
Why it matters and how to check
Sequential IDs make BOLA easy to exploit at scale: an attacker just counts upward. Random or time-ordered UUIDs make enumeration harder, which is useful, but an ID that leaks in a URL or log is still just an ID.
To verify: check that public-facing identifiers are not small sequential integers. Then repeat the two-user BOLA test from the previous check using a UUID copied from user A. It must still be refused.
Create and update endpoints accept only an allowlist of fields (mass assignment)Must
Why it matters and how to check
If an endpoint passes the request body straight into the database model, a user can set fields they should not, such as
role,isVerified,creditsorownerId. OWASP's API3 guidance is to avoid automatic binding of client input and allow changes only to the properties the client should update.To verify: for each create and update endpoint, send the normal body plus extra fields like
"role": "admin"and"credits": 9999. Re-fetch the object: none of the extra fields may have changed. In code, look for a schema that picks allowed fields.Example
TypeScript const UpdateProfile = z.object({ name: z.string().min(1).max(80), bio: z.string().max(500).optional(), }).strict(); // rejects unknown keys const input = UpdateProfile.parse(await req.json());Responses include only the fields the caller is allowed to seeMust
Why it matters and how to check
Returning the whole database row and letting the frontend hide fields leaks data to anyone who opens the network tab: password hashes, emails of other users, internal flags. OWASP says to avoid generic serializers like
to_json()and pick the properties to return.To verify: call each endpoint as a normal user and read the raw JSON, not the UI. Look for hashes, tokens, other users' personal data, internal notes and admin-only fields. Use explicit response DTOs or select lists so new database columns are not exposed automatically.
Admin and privileged functions check the caller role on the serverMust
Why it matters and how to check
Broken function level authorization (API5) is when a normal user can call
/admin/usersorDELETE /v1/projects/7because the endpoint only checks login, not role. Hiding the button in the UI is not a check.To verify: list every admin, staff and destructive endpoint. Call each one with a normal user's token. All must return
403. Prefer a middleware or route group that denies by default and requires an explicit role, so a new admin route cannot be added without the check.