API Security Checklist

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).

Show
  1. 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/1043 without 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 403 or 404. 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 });
  2. 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.

  3. 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, credits or ownerId. 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());
  4. 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.

  5. 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/users or DELETE /v1/projects/7 because 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.