API Scopes, Roles, and Object-Level Permissions
Compare API scopes, roles, and object-level permissions, then combine them to grant callers only the access each operation and resource requires.
API scopes limit actions a token may perform, roles group capabilities, and object-level policies check access to a specific record. This guide explains how to combine them with verified identity and trusted tenant or ownership data, including checks for list, bulk, and nested routes. Use its examples and tests to catch cross-tenant access gaps and keep authorization decisions observable without logging private data.
API Scopes, Roles, and Object-Level Permissions
Introduction
Scopes, roles, and object-level policies control access at different levels. A scope limits what a token may do, a role groups capabilities, and an object policy checks whether the caller may act on a specific record.
Suppose a token has projects:read, but the requested project belongs to another tenant:
GET /projects/project_42
Authorization: Bearer <token with projects:read>
The scope permits the read operation; it does not grant access to every project. Resolve the tenant from verified identity, then check the stored project’s tenant or owner before returning data. This is the difference between validating a broad permission and authorizing one object.
Roles and object-level rules
Roles work well for stable duties such as analyst or support agent. Object-level rules add context that roles and scopes cannot express by themselves, such as whether a project belongs to the caller’s tenant or a user is assigned to a case. Use these checks together: the scope permits an operation, the role provides a capability, and the object policy decides whether that caller may act on this record.
Authorization flow
flowchart TD
A[Request includes verified identity] --> B[Check required scope and role]
B --> C[Load resource and trusted tenant context]
C --> D[Check ownership or object policy]
D -->|Allowed| E[Return permitted data]
D -->|Denied| F[Return consistent safe response]
E --> G[Record decision without private payload]
Implementation practice
Apply authorization before returning each resource, including items in list, export, nested, and bulk routes. Derive tenant and ownership from verified identity and stored relationships, never from untrusted request fields. Test both allowed and denied cases across users and tenants, including alternate routes that expose the same records.
Production failure scenarios and mitigations
- A list or export route bypasses the detail authorization check. Apply the same object policy to every route that returns the resource, and test list, bulk, nested, and export behavior.
- A caller changes a tenant or owner ID in the request. Resolve those values from verified identity and stored relationships rather than trusting client-supplied fields.
- A valid scope exposes another tenant’s record. Treat scope as permission for an action, then verify access to each requested object before returning data.
- Different routes return 403 and 404 for the same protected object. Apply a consistent denial policy so responses do not reveal whether another tenant’s record exists.
Authorization model trade-offs
Scopes, roles, and object policies solve different problems. Choose the simplest combination that expresses the access rules without relying on client-supplied claims.
| Model | Best fit | Trade-off |
|---|---|---|
| Broad OAuth scopes | Separating capabilities between clients | Easy to issue and review, but too coarse for tenant or record boundaries |
| Fine-grained scopes | Integrations with distinct, narrow operations | Better least-privilege control, but token and consent configuration can grow quickly |
| RBAC | Stable job duties shared by many users | Straightforward assignment, but exceptions can multiply roles |
| ABAC or object policy | Access depends on tenant, ownership, or resource state | Precise decisions, but policy evaluation needs trusted context and careful testing |
Observability checklist
- Record allow and deny decisions by operation, policy, and resource type without logging protected payloads.
- Track repeated cross-tenant denials and unusual access to list, export, or bulk routes.
- Correlate denied requests with request IDs while keeping credentials and personal data out of logs.
- Monitor authorization latency and alert on sudden changes in denial rates after releases.
Security and Compliance Notes
- Resolve user, tenant, and service identity from a verified credential; treat request-body ownership fields as untrusted input.
- Record the actor, action, resource identifier, decision, and request ID for sensitive access decisions. Keep private payload fields out of the audit event and restrict who can read it.
- Review permission grants and service credentials periodically, and remove access when an integration or user no longer needs it.
- Map access and audit controls to the data and obligations in scope. Retention, access review, and breach-record requirements vary by jurisdiction and contract, so confirm them with the organization’s compliance owner.
Common Pitfalls / Anti-Patterns
- Treating a valid scope as proof of access to every object in that scope.
- Checking permissions only on detail routes while list, export, bulk, or nested routes return the same records unchecked.
- Trusting a client-provided role, tenant ID, or owner ID instead of deriving it from verified identity and stored resource relationships.
- Building a large collection of nearly identical roles to represent per-record exceptions; use a resource policy for those decisions.
- Returning different denial behavior across endpoints in a way that reveals whether another tenant’s record exists.
Quick Recap Checklist
- Use scopes for delegated capabilities and roles for application-level duties.
- Check access to each requested object on the server.
- Derive tenant and ownership context from trusted data.
- Test both allowed and denied cases across user and tenant boundaries.
Interview Questions
Further Reading
- API Authentication vs. Authorization — distinguish identity checks from permission decisions.
- API Keys, Sessions, and Service Credentials — choose and manage credentials for different callers.
- Input Validation, Secrets, and Sensitive Data — validate untrusted inputs and protect sensitive values.
- OWASP Authorization Cheat Sheet — practical access-control design and testing guidance.
- OWASP API Security: Broken Object Level Authorization — risks from missing per-object checks in APIs.
Conclusion
A valid token or role does not prove access to every object. Check the caller’s scope and role, then verify tenant, ownership, or other resource rules using trusted data on every route. Tests across users, tenants, list operations, and bulk actions help catch gaps before they expose another caller’s records.
Category
Related Posts
CSRF, CORS, and SSRF: Defending Web Request Boundaries
Learn how CSRF, CORS, and SSRF differ, then apply cookie, origin, allowlist, and egress controls to protect browser and server request boundaries.
API Authentication vs. Authorization: Identity and Access
Understand API authentication and authorization, how they differ in request handling, and how to avoid common identity and access-control mistakes.
API Input Validation, Secrets, and Sensitive Data
Validate API inputs at trust boundaries, handle secrets safely, and limit sensitive data exposure in logs, storage, and error responses.