Authorization models and where auth lives
We'll cover the following
Models for deciding what a user may do
| Model | How it decides | Strength | Weakness |
|---|---|---|---|
| RBAC (role-based) | Permissions attach to roles, users get roles: viewer reads, editor reads and writes, admin also deletes | Simple, easy to explain and audit. The default answer. | Role explosion: "managers approve expenses under $5K in their own department" needs a role per department and amount band |
| ABAC (attribute-based) | Rules over attributes of the user, the resource and the context | Very flexible and fine-grained | Harder to reason about, test and audit |
allow if user.department == resource.department
AND amount < 5000
AND time is within business hours- ABAC rules are usually run through a policy engine such as OPA, so they live in one place instead of being scattered through service code.
- ReBAC (relationship-based) in one line: access follows relationships, like "you can edit this doc because you are in the team that owns the folder". Google Drive style, from Google Zanzibar, open-source versions such as SpiceDB.
- Rule of thumb: RBAC for coarse permissions, plus ownership or attribute checks where RBAC is not enough. Most real systems are exactly that.
Where auth lives: gateway or services?
Both, doing different jobs.
| Layer | Job |
|---|---|
| Gateway | Authentication plus coarse checks: verify the token (signature, expiry, denylist), reject bad tokens early, check broad scopes ("does this token allow orders:read at all?"), apply rate limits. Then forward the verified identity downstream as a header or a short internal token. |
| Each service | Fine-grained, resource-level authorization. The service owns the data, so it decides "can user 42 view order 7?" |
- Why not gateway-only: the gateway cannot see business data (it does not know order 7 belongs to someone else).
- Zero trust: internal services should not blindly trust whatever arrives from inside the network. Use mTLS between services so one compromised service cannot impersonate anyone.
- When rules get complex, centralize policy in an engine like OPA so every service asks the same source of truth instead of re-implementing the rules.
IDOR / BOLA: the check people forget
User 42 is logged in with a perfectly valid token and calls GET /orders/7, an order that belongs to user 99. The token is valid, so the gateway lets it through. Only the service can block it, because only it knows who owns order 7.
- The check: after loading the order, compare
order.owner_idwith the user id from the verified token. Or make the query do it:WHERE id = 7 AND owner_id = 42, so a foreign order is simply not found. - The bug has a name: IDOR (insecure direct object reference), also called BOLA (broken object level authorization). It is #1 on the OWASP API Security list. It happens when a service checks "is the token valid?" but never "is this your object?".
- Take the user id from the verified token, never from the request. If the client sends
user_id=42in the body, an attacker just changes it. - Return 404, not 403, for objects the user should not know exist. A 403 confirms "order 7 exists, but it is not yours".
- Random ids (UUIDs) are harder to guess than sequential ids, but only a speed bump. The ownership check is the real fix.
- Test for it: log in as user A and request user B's ids. Many breaches come from this one missing check.
Check yourselfUser 42 has a valid token and calls GET /orders/7, an order owned by user 99. Which layer should block it, and what does it check?
The service that owns orders. It compares order.owner_id with the user id taken from the verified token (or queries with WHERE id = 7 AND owner_id = 42), and answers 404 so it does not reveal that the order exists. This is IDOR / BOLA, OWASP API #1.
Summary: say it in the interview
- "The gateway authenticates and enforces coarse scopes. Services enforce resource-level authorization, RBAC plus ownership checks, and I would centralize policy in something like OPA if the rules get complex."