Module 04 · API fundamentals · Lesson 11 of 15
Authentication vs authorization
Who are you, and what are you allowed to do: two different questions answered at two different points in the request path. Where each check lives in a system, the models for expressing permissions, and the mistakes that leak data.
Why it matters
Most data leaks are not broken encryption; they are an endpoint that checked who you were and forgot to check whether you were allowed to see that particular record. Separating authentication from authorization, and knowing where each belongs in the request path, is the foundation every API security lesson in this module builds on.
The idea
Authentication (authn) establishes identity: this request comes from user 42, or from service billing. Authorization (authz) decides permission: user 42 may read order 9001 because they placed it, and may not read order 9002 because someone else did. Authentication happens once per request at the edge; authorization happens at every point where a decision depends on who is asking, which is usually deep in the service, next to the data.
How identity is established
Credentials come in a few forms: something the user knows (password), has (device, hardware key, one-time code) or is (biometric). For APIs, the credential is usually a token issued after one of those checks: a session id, a signed JWT, an API key for a program, or a certificate for service-to-service calls (mutual TLS). The lessons that follow cover sessions, tokens, JWTs and the OAuth flows that issue them. Whatever the mechanism, the output is a principal: a trusted identity plus attributes (roles, tenant, scopes) attached to the request for the rest of its journey.
How permission is expressed
| Model | Rule shape | Fits |
|---|---|---|
| RBAC (role-based) | Users have roles; roles have permissions: 'admins can delete orders' | Most applications; simple, auditable |
| ABAC (attribute-based) | Policies over attributes of user, resource and context: 'owner may edit while status is draft and it is business hours' | Fine-grained rules, multi-tenant, regulatory |
| ReBAC (relationship-based) | Permissions derived from a graph of relationships: 'viewers of a folder can view its documents' | Sharing-heavy products (docs, drives); Google Zanzibar style |
| ACLs | Per-resource list of who can do what | Small sets of resources; becomes unmanageable at scale |
Most systems start with RBAC for coarse permissions and add ownership checks (ABAC in miniature) for records: "role allows reading orders, and this order belongs to this user's tenant". Centralising the decision in one policy module or service keeps rules consistent and testable instead of scattered across handlers.
The classic mistakes
- Checking authn and skipping authz: any logged-in user can fetch
/orders/9002by changing the id. Called broken object-level authorization, it tops every API vulnerability list. - Authorizing in the client: hiding a button is not a permission check. The API must enforce it.
- Trusting forwarded identity without verification: internal services accepting a
X-User-Idheader from anyone on the network. Verify the token or use a mesh that does. - Overbroad principals: one API key with every scope, shared by three teams. Least privilege: issue narrow scopes and rotate keys.
Trade-offs
Checking authorization at the edge is fast and simple and can only enforce coarse rules, because the edge does not know who owns order 9001. Checking next to the data is precise and must be done consistently in every handler. A central policy service gives consistency and adds a dependency on the hot path, usually mitigated with a local cache or sidecar.
In an interview
Say both words and where each lives: "The gateway authenticates the token and attaches the principal; each service authorizes against the resource, so reading an order checks that the order's tenant matches the caller's." Then name the model: "RBAC for admin versus user, plus ownership checks per record." Mention least privilege for service accounts if the design has internal calls.
Check yourself
A logged-in user changes the id in /api/invoices/1001 to 1002 and sees another customer's invoice. Which check was missing?