Product
Enterprise
Solutions
DocumentationPricing
Resources
Book a DemoSign InGet Started
Product
Solutions
Resources
Blog |API Authorization 101: Who Can Do What?

API Authorization 101: Who Can Do What?

API Design  |  Nov 17, 2025  |  20 min read  |  By Savan Kharod

Summarize with
API Authorization 101: Who Can Do What? image

Savan Kharod works on demand generation and content at Treblle, where he focuses on SEO, content strategy, and developer-focused marketing. With a background in engineering and a passion for digital marketing, he combines technical understanding with skills in paid advertising, email marketing, and CRM workflows to drive audience growth and engagement. He actively participates in industry webinars and community sessions to stay current with marketing trends and best practices.

When people talk about API security, they usually lump everything under “auth,” but there are really two separate jobs: authentication and API authorization. Authentication is about proving who is calling your API; authorization is about deciding what that caller is allowed to do once they’re in.

A simple way to think about it is a secure office building:

  • Authentication is the front desk checking your badge to confirm you’re an employee.

  • Authorization is what happens at the turnstiles and doors: this badge opens the 3rd floor, but not the finance vault.

Your APIs work the same way. Authentication verifies the identity behind the request, API keys, JWTs, OAuth tokens, etc. Authorization then decides whether that identity can read this record, update that resource, or call a specific admin endpoint.

In this article, we’ll focus on understanding the key difference between API authorization vs authentication, and how to design access control in REST APIs for each.

We’ll walk through the main models you’ll actually use in production, roles (RBAC), attributes (ABAC), and OAuth scopes, then show how to implement simple checks in code, handle failures cleanly, and keep authentication (authN) and authorization (authZ) properly separated in your architecture.

What Is API Authorization?

At a practical level, API authorization is the step where your backend decides whether an authenticated caller is allowed to perform a specific action on a specific resource. After authentication has verified who the caller is, API authorization evaluates their roles, permissions, scopes, and other attributes to grant or deny access to routes, operations, and data.

You can think of this as the access control layer in REST APIs: given a request like DELETE /users/123, the authorization answers questions such as:

  • Is this user allowed to delete any account?

  • If not, are they at least allowed to delete their own account?

  • Are there extra constraints (e.g., feature flags, plan limits, tenant boundaries)?

Where authentication is binary (“is this token valid?”), API authorization is about granularity: “what operations, on which resources, under which conditions, are permitted for this identity?” Modern APIs usually implement this with a combination of user roles and permissions, RBAC (role-based access control), OAuth scopes, or more expressive policy engines. 

Concretely, API authorization shows up in everyday use cases like:

  • Admin-only operations

    • Only users with an admin role can hit destructive endpoints like DELETE /users/:id or change global configuration.
  • Per-user data isolation

    • A user can call GET /users/:id only when :id matches their own user ID, or they hold a higher-privileged role.
  • Plan- or tier-based features

    • Free plans can call GET /reports/summary, but not GET /reports/detailed; premium plans get extra endpoints or higher rate limits.
  • Tenant or organization boundaries

    • A user can see invoices only for their own org_id, even if they know another organization’s IDs.
  • Field- and action-level controls

    • Support agents can view a user profile but not see full payment details; finance users can see billing fields but cannot reset passwords.

Under the hood, every authorization decision is typically based on four ingredients:

  1. Subject: who is calling (user, service, API client) and what attributes they have (ID, roles, groups, scopes, tenant, risk level).

  2. Resource: what is being accessed (endpoint, object, field), such as a specific user record or invoice.

  3. Action: what operation is requested (read, write, delete, approve, export).

  4. Context: extra factors like time of day, IP range, region, or device type.

API authorization evaluates these pieces against your access control rules or policies and then either allows the call to proceed or blocks it with an error (usually 403 Forbidden). When this layer is missing or misconfigured, you get broken access control, where users can act outside their intended permissions, which OWASP highlights as one of the most critical classes of web and API vulnerabilities.

Authorization Models Used in APIs

Once authentication has identified who is calling, API authorization requires a consistent method to determine what that caller can do. In practice, most teams lean on three core models:

  • Role-Based Access Control (RBAC): permissions tied to roles

  • Attribute-Based Access Control (ABAC): permissions driven by attributes and policies

  • Scope-based access (OAuth scopes): permissions encoded into tokens

You can use them individually or combine them to build robust access control in REST APIs.

Role-Based Access Control (RBAC)

Role-Based Access Control (RBAC) is the “classic” API authorization model: instead of assigning permissions directly to each user, you define roles, associate permissions with those roles, and then assign roles to users.

RBAC implements least privilege: each role gets only the access it needs to perform its job, reducing the blast radius of compromised accounts and simplifying audits.

Benefits of implementing RBAC:

  • Clear user roles and permissions (matches org structure and job titles).

  • Straightforward to understand, debug, and enforce in REST APIs.

  • Works well for coarse-grained access: “admins vs normal users.”

Limitations of implementing RBAC:

  • Complex enterprises with many edge cases (“admin in region X, read-only in Y”).

  • Multi-tenant or B2B systems where rules depend on who owns what and context.

  • Growing sets of roles that explode into “role-per-permutation” (sales-eu-manager, sales-us-manager, etc.).

Attribute-Based Access Control (ABAC)

ABAC takes API authorization one step further by using attributes and policies rather than just roles. So instead of asking “Does this user have the admin role?”, ABAC asks:

“Given the user, the resource, the action, and the environment, do the attributes satisfy the policy?”

NIST defines ABAC as a logical access control model where authorization to perform operations is determined by evaluating attributes of the subject, object, action, and sometimes environment, against a set of policies.

In practice, that means you describe access rules in terms of:

  • Subject attributes, who is calling:
    user.id, user.orgId, user.department, user.clearance, user.country, user.isOnCall.

  • Resource attributes, what they are touching:
    resource.orgId, resource.ownerId, resource.type, resource.classification.

  • Action attributes, what they want to do:
    action = 'read' | 'write' | 'delete' | 'approve' | 'export'.

  • Environment attributes, context:
    timeOfDay, ipRange, geoRegion, riskScore, deviceTrustLevel.

Policies then look like if-then rules:

  • If user.orgId === resource.orgId and action === 'read'allow

  • If user.department === 'finance' and amount < 10000 and timeOfDay in business hours → allow approve

  • If user.country not in ['US','EU'] and resource.classification === 'PII'deny

ABAC is especially attractive for fine-grained, context-aware API authorization, where simply knowing someone’s role is not enough.

Benefits of implementing ABAC

  • Provides fine-grained, context-aware API authorization using user, resource, action, and environment attributes.

  • Policies can be reused across services and resources, improving consistency of access control.

  • Models complex enterprise rules more naturally than large role hierarchies (e.g., tenant, region, sensitivity).

Limitations of implementing ABAC

  • Higher implementation complexity: requires clear attribute models, policy language, and decision engine.

  • Policies can become hard to reason about or debug without good tooling and test coverage.

  • Performance overhead if policy evaluation is not carefully designed or cached.

Scope-Based Access (OAuth Scopes)

Where RBAC and ABAC are usually user-centric, OAuth scopes are token-centric: they describe what a particular access token is allowed to do in your API.

Scopes are usually simple strings, each representing a capability or permission set, for example: 

  • read:user – read user profile data

  • write:account – modify account information

  • payments:charge – create new charges

  • invoices:read – read invoices

  • invoices:read:own – read invoices that belong to the caller

In a typical OAuth-protected API:

  1. The client (mobile app, SPA, partner integration) requests scopes during authorization.

  2. The user/admin sees a consent screen: “This app wants to read your profile and manage your invoices.”

  3. The authorization server issues an access token embedding those scopes.

  4. Your API or API gateway checks: Does this token have the required scopes for this endpoint and method?

This is still API authorization, just expressed at the token level rather than the user-record level. The same human user might have admin rights in your product, but if a particular token has only read:user, then that token cannot hit admin routes.

Benefits of implementing scope-based access

  • Encodes permissions directly into tokens, enabling least-privilege access per client or integration.

  • Provides a standard, interoperable way to limit third-party and first-party app access in OAuth/OIDC flows.

  • Makes it easier to reason about what a particular token can do, independent of the full user profile.

Limitations of implementing scope-based access

  • Poorly designed scopes can become too coarse (“full_access”) or too granular, making management difficult.

  • Scopes typically describe capabilities, not contextual rules (tenant boundaries, ownership, time-based limits).

  • Requires consistent scope validation in all APIs and services that consume tokens.

Protect your APIs from threats with real-time security checks.

Treblle scans every request and alerts you to potential risks.

Explore Treblle
CTA Image

Protect your APIs from threats with real-time security checks.

Treblle scans every request and alerts you to potential risks.

Explore Treblle
CTA Image

How to Implement Basic Authorization Logic

Once you have authentication in place, API authorization becomes a matter of consistently enforcing rules like:

“Only admins can delete users, and users can delete only their own account.”

Let’s walk through that exact example for DELETE /users/:id, and then generalize the pattern.

Define the Rule for DELETE /users/:id

Business rule:

  • Allow the request only if:

    • user.role === 'admin' or

    • user.id === params.id (the user is deleting their own account)

Everything in your authorization layer should be a clear, testable rule like this. Avoid “magic” conditions scattered across controllers.

Assume Authentication Has Already Run

First, you need authentication middleware that:

  • Validates the token/session/API key.

  • Attaches an identity object to the request (for example req.user in Express).

Example shape:

// after authentication
 
req.user = {
 
  id: 'abcd-1234',
 
  role: 'member', // or 'admin'
 
  // ...other attributes if needed
 
};

From here on, API authorization does not care how the user was authenticated, only about the resulting user object.

Express.js: Authorization as Middleware

A clean pattern in Node.js/Express is to implement authorization as dedicated middleware, separate from the route handler.

Step 1: Authentication middleware (simplified)

// auth.js
 
const jwt = require('jsonwebtoken');
 
function authenticate(req, res, next) {
 
  const header = req.headers.authorization || '';
 
  const token = header.startsWith('Bearer ') ? header.slice(7) : null;
 
 
  if (!token) {
 
    return res.status(401).json({ error: 'Missing access token' });
 
  }
 
  try {
 
    const payload = jwt.verify(token, process.env.JWT_SECRET);
 
 
    // Only the minimal fields needed for API authorization
 
    req.user = { id: payload.sub, role: payload.role };
 
    return next();
 
  } catch (err) {
 
    return res.status(401).json({ error: 'Invalid or expired token' });
 
  }
 
}
 
module.exports = { authenticate };

This handles authentication, not authorization.

Step 2: Authorization middleware for DELETE /users/:id

// authz.js
 
function canDeleteUser(req, res, next) {
 
  const authUser = req.user;          // set by authenticate()
 
  const targetUserId = req.params.id; // /users/:id
 
  if (!authUser) {
 
    // Should normally be caught by authenticate(), but guard anyway
 
    return res.status(401).json({ error: 'Unauthenticated' });
 
  }
 
 
  const isAdmin = authUser.role === 'admin';
 
  const isSelf  = authUser.id === targetUserId;
 
  if (!isAdmin && !isSelf) {
 
    // Authenticated but not allowed → API authorization failure
 
    return res.status(403).json({ error: 'Forbidden' });
 
  }
 
 
  // Authorization successful; move to the actual handler
 
  return next();
 
}
 
 
module.exports = { canDeleteUser };

Step 3: Wire it into the route

// users.routes.js
 
const express = require('express');
 
const router = express.Router();
 
const { authenticate } = require('./auth');
 
const { canDeleteUser } = require('./authz');
 
 
// DELETE /users/:id
 
router.delete('/users/:id', authenticate, canDeleteUser, async (req, res) => {
 
  const { id } = req.params;
 
 
  // Business logic: delete from DB, emit events, etc.
 
  await deleteUserFromDb(id);
 
  return res.status(204).send(); // No Content
 
});
 
module.exports = router;

What this gives you:

  • Separation of concerns

    • authenticate → “Who is this?”

    • canDeleteUser → “Is this user allowed to delete this account?”

    • Route handler → business logic only.

  • Easier testing: you can unit-test canDeleteUser with simple mocks for req.user and req.params.id.

  • Clear, reusable API authorization rules that are not buried in controller code.

Generalizing the Middleware Pattern (RBAC Example)

For role-based checks, you can create generic helpers and reuse them across routes:

// authz-role.js
 
function requireRole(...allowedRoles) {
 
  return function (req, res, next) {
 
    const user = req.user;
 
 
    if (!user) {
 
      return res.status(401).json({ error: 'Unauthenticated' });
 
    }
 
 
    if (!allowedRoles.includes(user.role)) {
 
      return res.status(403).json({ error: 'Forbidden' });
 
    }
 
    return next();
 
  };
 
}
 
 
module.exports = { requireRole };

Usage:

const { authenticate } = require('./auth');
 
const { requireRole } = require('./authz-role');
 
// All /admin routes require authentication + admin role
 
app.use('/admin', authenticate, requireRole('admin'), adminRouter);

This keeps role logic centralized and makes your access control in REST APIs much easier to audit.

By consistently applying this middleware pattern, you turn “who can do what” into a set of small, composable units. That keeps your API authorization predictable, testable, and easy to evolve as you add new roles, scopes, or policies.

Protect your APIs from threats with real-time security checks.

Treblle scans every request and alerts you to potential risks.

Explore Treblle
CTA Image

Protect your APIs from threats with real-time security checks.

Treblle scans every request and alerts you to potential risks.

Explore Treblle
CTA Image

How to Handle Authorization Failures

Authorization failures are normal in any secured API. The key is to handle them consistently, safely, and in a way that’s easy for both clients and operators to reason about.

Use the Right HTTP Status Codes

Always distinguish between authentication and authorization failures in your responses:

  • 401 Unauthorized (actually “Unauthenticated”): Use this when the client has not provided valid credentials. According to MDN and HTTP specs, 401 means the client must authenticate itself to get the requested response.

    • Examples: missing Authorization header, invalid/expired token.
  • 403 Forbidden: Use this when the request is understood and the user is authenticated, but your API authorization logic denies access. Further authentication will not change the outcome.

    • Examples: user lacks the required role, scope, or fails a policy check.

This distinction helps client developers debug correctly and aligns with HTTP semantics.

Return Safe, Consistent Error Bodies

For access control failures, aim for:

  • A stable JSON structure (e.g., { "error": "Forbidden", "reason": "missing_permission:user:delete" }).

  • Messages that are clear enough for legitimate clients but do not leak sensitive details (stack traces, SQL, internal IDs, or policy internals).

OWASP recommends generic error messages for security-related failures to avoid exposing implementation details that could aid an attacker.

Good patterns:

// 401 example
 
{
 
  "error": "Unauthenticated",
 
  "message": "Valid access token is required."
 
}
 
 
 
 
// 403 example
 
{
 
  "error": "Forbidden",
 
  "message": "You do not have permission to perform this action."
 
}

If you need more detail for internal clients, prefer machine-readable reasons (code, reason) over verbose human text that reveals too much.

Centralize Authorization Failure Handling

Avoid scattering res.status(403)... or equivalent all over your codebase. Instead:

  • Centralize API authorization checks (middleware, decorators, or a shared helper).

  • Centralize error handling so failed checks are processed in one place.

OWASP’s Authorization Cheat Sheet explicitly recommends centralizing failed access-control handling to avoid inconsistent behavior or edge cases that could lead to bypasses.

In practice:

  • Middleware throws or returns a standardized “not allowed” error.

  • A global error handler converts that into a 403 response with your canonical JSON format.

Log and Audit Authorization Failures

From a security perspective, denied requests are as important as successful ones:

  • Log who attempted the action (user ID / client ID, tenant).

  • Log what they tried to do (method, path, resource ID).

  • Log why it failed (missing role, missing scope, failed policy).

OWASP’s logging and monitoring guidance recommends capturing access control failures with enough context to identify suspicious behavior and support incident response.

You can then:

  • Detect brute-force ID enumeration (repeated 403s on different object IDs).

  • Spot misconfigured roles or policy bugs.

  • Correlate with other events in your observability stack.

Don’t Leave the System in an Inconsistent State

When an authorization check fails:

  • Abort the operation as early as possible, before any stateful side effects (writes, external calls, queued jobs).

  • Ensure the code path exits cleanly.

OWASP notes that improper handling of failed access-control checks can leave an application in an unpredictable or insecure state.

A good pattern is:

  1. Authenticate → attach user to request.

  2. Authorize → if denied, immediately return 403.

  3. Only if authorized → execute business logic.

Handled well, authorization failures become a predictable part of your API authorization story: clients get clear signals (401 vs 403), attackers learn nothing useful from responses, and your logs give you full visibility into “who tried to do what and was blocked.”

Best Practices for API Authorization

Good API authorization is more than sprinkling if (user.role === 'admin') across handlers. You want access control in your REST APIs to be consistent, explainable, and easy to evolve as roles, teams, and features change. OWASP classifies broken access control as one of the most critical risks, precisely because ad-hoc authorization is so easy to get wrong.

The API Authorization best practices below give you a durable structure for “who can do what” without turning every change into a migration project.

1 Design Around Least Privilege and Central Ownership

Start by treating least privilege as a design constraint, not an afterthought: each user, service, and token should have only the access necessary to perform its task. NIST explicitly calls this out as a core control for access management.

In practical terms, that means:

  • Define roles, permissions, and policies once, in a central source of truth (an IAM service, database, or config), rather than hardcoding them per service.

  • Default to deny for anything that is not explicitly allowed.

  • Keep role and permission definitions auditable so you can answer “who can do what?” without grep across microservices.

When least privilege and centralization are in place, changing access is mostly configuration work rather than editing business logic everywhere.

2 Keep Authentication and Authorization Separate

Authentication and authorization solve different problems and should be implemented in separate layers:

  • Authentication (authN) verifies the caller (API key, JWT, OAuth token, session).

  • Authorization (authZ) decides which actions and resources that caller is allowed to use.

In code, this typically looks like:

  1. An authN middleware/guard that validates credentials and attaches identity (user, claims) to the request.

  2. A separate authZ middleware/policy call that uses that identity (roles, scopes, attributes) to allow or deny specific operations.

OWASP explicitly notes that access control checks must be performed after authentication and in centralized, server-side logic.

This separation keeps the API authorization logic testable, reusable, and much less likely to be bypassed by accident.

3 Express Rules as Policies, Not Scattered Conditionals

Ad-hoc if statements like “if (user.role === 'manager' && user.department === 'finance' && amount < 10000) { ... }” don’t scale; over time, they become hard to audit and easy to misalign across services.

A more robust pattern is to express authorization rules as policies:

  • Define rules in a policy engine, config file, or dedicated authorization service (RBAC/ABAC).

  • Evaluate them via a single decision point, e.g. authorize(user, action, resource, context).

  • Keep policy definitions under version control and test them like code.

By externalizing rules, you can change who can do what without redeploying every API, and you reduce the risk of subtle differences between services.

4 Use Scopes and Claims Deliberately for Token-Based Access

When you rely on OAuth 2.0 or OpenID Connect, scopes and token claims are core inputs to API authorization:

  • Scopes describe what the token is allowed to do (e.g. read:user, payments:charge).

  • Claims carry identity and attributes (roles, tenant IDs, org IDs) that your RBAC/ABAC rules depend on.

Good practice here includes:

  • Designing scopes around business capabilities rather than arbitrary strings, and requesting only the minimal scopes needed.

  • Enforcing required scopes at the API or gateway level for each endpoint.

  • Combining scopes with internal roles/policies: the token might have invoices:read, but policies still decide which invoices are visible to this caller.

Used this way, scopes and claims help you maintain least privilege per token, not just per user.

5 Log Decisions, Monitor Patterns, and Limit Data Exposure

Authorization is not just about blocking calls; it is also about knowing what is happening and limiting blast radius when something goes wrong.

From a monitoring perspective:

  • Log both successful and failed authorization decisions: who, what, and why the decision was made.

  • Feed these logs into your observability stack so you can detect anomalies (spikes in 403s, repeated access to admin routes, unusual tenants or regions).

From a data-exposure perspective:

  • Return only the fields the caller genuinely needs; don’t expose full internal models by default.

  • Apply field-level controls or masking for sensitive data (PII, payment details, secrets), so even correctly authorized users see only what their role requires.

OWASP’s guidance on broken access control and logging makes it clear: robust API authorization is not just a gate, but a combination of strict rules, visibility into how they are used, and restraint in what data each caller is allowed to see.

See how your APIs are adopted and used with clean, actionable data.

Treblle gives you dashboards and insights built for APIs.

Explore Treblle
CTA Image

See how your APIs are adopted and used with clean, actionable data.

Treblle gives you dashboards and insights built for APIs.

Explore Treblle
CTA Image

Conclusion: Don’t Skip Authorization

If authentication tells you who is calling your API, API authorization is everything that follows: the rules, checks, and guardrails that decide who can do what, where, and under which conditions across your system. Most real-world breaches like the recent FIA Cyber Breach are often around APIs that don’t come from missing logins; they come from broken access control, users being able to see or do more than they should because authorization was bolted on as an afterthought.

OWASP explicitly lists Broken Access Control as a top application and API risk for exactly this reason.

Start with clear, testable rules (“admins can delete any user”, “users can only read their own data”), implement them using a combination of RBAC, ABAC, and OAuth scopes, and keep authentication and authorization as separate layers in your architecture.

As your product grows, you can evolve from simple role checks to policy-driven, attribute-based decisions without rewriting every handler, as long as you’ve treated API authorization as a first-class concern from the start.

Where tools like Treblle help is in everything that happens around those checks:

  • Full-fidelity observability for every request, so you can see which roles, tokens, and clients are hitting which endpoints, with which status codes, in real time.

  • Access and error analytics, to spot patterns like repeated 403s, unusual access to sensitive routes, or tenants that suddenly expand their footprint.

  • Sensitive data masking and compliance awareness, so even when authorization is correct, you are not over-exposing PII or regulated fields in logs, traces, or responses, which is critical for GDPR/PCI-style obligations.

The net result is that authorization stops being a black box. You can define “who can do what” in code and policies, and then see how those decisions play out in production: which rules are hit, who was blocked, where access patterns look suspicious, and where data might need tighter control.

Even if your API feels “simple” today, investing in a clear authorization model plus runtime visibility pays off quickly. It reduces the risk of silent privilege creep, makes audits and debugging easier, and gives you the confidence to ship new endpoints knowing you can enforce and observe the right boundaries from day one.

Protect your APIs from threats with real-time security checks.

Treblle scans every request and alerts you to potential risks.

Explore Treblle
CTA Image

Protect your APIs from threats with real-time security checks.

Treblle scans every request and alerts you to potential risks.

Explore Treblle
CTA Image

Related Articles

API Rate Limiting vs Throttling: What’s the Difference? coverAPI Design

API Rate Limiting vs Throttling: What’s the Difference?

Rate limiting sets hard caps on how many requests a client can make; throttling shapes how fast requests are processed. This guide defines both, shows when to use each, and covers best practices.

How API Sprawl Creates Shadow APIs (And How to Stop It) coverAPI Design

How API Sprawl Creates Shadow APIs (And How to Stop It)

Unmanaged API growth produces shadow endpoints you can’t secure or support. This guide explains how sprawl creates blind spots, the security and compliance risks, and a practical plan to stop it at the source.

Understanding API Caching Architecture for Enterprise Systems coverAPI Design

Understanding API Caching Architecture for Enterprise Systems

API caching is more than a speed boost, it’s a strategic advantage. In this guide, we break down enterprise caching architecture, key performance benefits, and how tools like Treblle can help teams monitor and optimize their caching layers.

© 2025 Treblle. All Rights Reserved.
GDPR BadgeSOC2 BadgeISO BadgeHIPAA Badge