Authorization Best Practices

Chapter 2 · Practice 3 of 4

Authorize fields too: what can be read and what can be written

From Object-level authorization: stop IDOR and BOLA

Why it matters

Object-level checks can still leak through individual properties. ASVS 5.0 requirement 8.2.3 covers field-level access, which the API Security Top 10 calls broken object property level authorization (BOPLA). It has two directions:

Reading too much. A GET /users/me that returns the whole database row exposes password hashes, internal flags or another user's email in a nested object. Shape every response with an explicit allowlist of fields (a DTO, a serializer, a select), so new columns are private until someone adds them.

Writing too much (mass assignment). An update handler that passes the request body straight to the ORM lets a user send {"role": "admin"} or {"accountId": "other"}. Accept only the fields that this user may change, for this object, in its current state.

ASVS 8.1.2 asks you to document these field rules, including when they depend on state: a submitted expense report can no longer have its amount changed by the submitter, while the approver can change its status.

Example

TypeScript
const EDITABLE_BY_ROLE = {
  member: ['displayName', 'avatarUrl', 'timezone'],
  admin: ['displayName', 'avatarUrl', 'timezone', 'role', 'disabled'],
} as const;

export function pickEditable(body: Record<string, unknown>, role: keyof typeof EDITABLE_BY_ROLE) {
  const allowed = new Set<string>(EDITABLE_BY_ROLE[role]);
  return Object.fromEntries(Object.entries(body).filter(([key]) => allowed.has(key)));
}