All knowledge articles

Securing API Authentication and Authorization

How token-based API authentication and authorization actually fail in practice, and a repeatable procedure for reviewing object- and function-level checks before they do.

AuthenticationAccess ControlApplication Security

API authentication answers 'who is calling?'. API authorization answers 'what is this caller allowed to do, to this specific resource, right now?'. Most real-world API incidents are not failures of the first question — they are failures of the second, applied inconsistently to individual objects and individual functions rather than to the endpoint as a whole.

API authentication and authorization flowA client presents a bearer token to an API gateway, which forwards it to an auth service for validation, which allows the request to reach a resource server that performs its own object-level authorization check. Interactive: switch between the normal authorized path and the failure path, where a forged, expired, or over-scoped token — or a request for another subject's object — is denied. Explore each node for details.ClientAPIgatewayAuthserviceResourceserverRequestdenied

Hover or focus a node to explore it.

Client → API gateway → auth service → resource server. The auth service validates token integrity and scope; the resource server independently enforces object-level authorization before returning data.
01

Executive summary

This guide walks through a token-based authentication and authorization flow for a fictional API, shows where the two most common and most damaging authorization mistakes actually occur, and gives a repeatable set of checks a developer or reviewer can apply to a real API design without needing a lab environment.

02

What you will learn

  • The difference between authentication, coarse-grained (function-level) authorization, and fine-grained (object-level) authorization, and why conflating them creates blind spots.
  • How short-lived bearer tokens are issued, validated, and rejected in a typical OAuth2/OIDC-style flow, including which fields (issuer, audience, expiry, scope) actually carry security meaning.
  • Why broken object-level authorization (BOLA) and broken function-level authorization are the two most common ways APIs fail even when authentication is implemented correctly.
  • A concrete, repeatable procedure for reviewing an API's authentication and authorization design, including what evidence to require before trusting a control.
03

Intended audience

  • Developers designing or implementing API endpoints that accept a bearer token or session credential.
  • Security practitioners reviewing an API's authorization model before or after release.
  • Technical leads who need a shared vocabulary for discussing 'is this endpoint secure' with more precision than that question usually gets.
04

Problem or security question

It is easy to confirm that an API rejects requests with no token, and easy to stop there. That confirms authentication works. It says nothing about authorization: whether a validly authenticated caller can be tricked into accessing another user's data (object-level authorization), or into calling an administrative function their role should not reach (function-level authorization). Both failures produce a '200 OK' response, not an obvious error — which is exactly why they are found in production more often than broken authentication is.

A second, quieter problem is treating a token's mere validity as proof of authorization. A cryptographically valid, unexpired token only proves who issued it and who it was issued to. It does not by itself prove the token's subject should be allowed to read or modify the specific resource named in the request path or body.

05

Threat model or relevant risk

Consider a fictional expense-reporting API we'll call the Meridian Ledger API, reachable at `api.lab.example.com`. Clients authenticate once against an authorization server at `auth.lab.example.com` and receive a short-lived access token, which they present as a bearer token on every subsequent request.

Relevant adversaries: (1) an authenticated user of the system attempting to read or modify another user's expense records by changing an identifier in the request (an object-level authorization attack, not a credential-theft attack); (2) a caller with a narrowly scoped token attempting to reach an endpoint that requires a broader scope or an administrative role (a function-level authorization attack); (3) an attacker presenting an expired, forged, or wrong-audience token in the hope that a component downstream of the edge fails to re-validate it.

Out of scope for this guide: transport-layer attacks (covered by TLS/PKI practice generally), credential-stuffing and password attacks against the authorization server's login flow, and infrastructure-level compromise of the authorization server itself. This guide assumes the authorization server correctly authenticates the end user and focuses on what happens to the token afterward.

06

Main technical content

**Authentication issues the credential; authorization decides what it's good for.** In the Meridian Ledger example, the client authenticates against `auth.lab.example.com` and receives a signed access token. The token is proof of identity and of the scopes the resource owner granted — it is not, by itself, proof that the bearer may act on any particular expense record. Treating 'token is valid' and 'action is authorized' as the same check is the single most common root cause behind API authorization failures.

**Validate the token fully, every time, at the boundary that enforces trust.** A well-formed token check confirms: the signature was produced by the expected authorization server (not merely that a signature is present); the token has not expired; the `aud` (audience) claim names this API, not some other service that happens to trust the same authorization server; and the granted scopes are sufficient for the requested operation. Skipping the audience check is a quietly common mistake — it lets a token legitimately issued for one API be replayed against a different API that trusts the same issuer.

**Function-level authorization: does this caller's role reach this endpoint at all?** This is the coarse-grained check — does a token with `expenses:read` scope get anywhere near an endpoint that approves reimbursements? Function-level checks are usually easy to test because they don't depend on which specific record is involved; a reviewer can enumerate every endpoint and ask 'what scope or role does this require, and is that enforced on the server side, not just hidden from a client's menu.'

**Object-level authorization: does this caller's role reach this specific object?** This is the check that most often gets skipped, because it requires re-verifying ownership on every single request rather than once at login. In the Meridian Ledger example, `GET /expenses/{id}` must confirm that the expense identified by `{id}` belongs to (or is otherwise visible to) the authenticated subject — not merely that the subject holds a valid `expenses:read` token. Changing `{id}` to a neighboring value and receiving another user's data back is the textbook broken object-level authorization (BOLA) failure, and OWASP's API Security Top 10 lists it as the most frequently reported API weakness for consecutive editions.

**Short-lived tokens reduce, but do not eliminate, the blast radius of a leaked token.** A short expiry limits how long a stolen or over-shared token remains useful; it does not substitute for object- and function-level checks, and it does not prevent misuse during its valid lifetime. Pair short expiry with the ability to revoke a specific token or session before its natural expiry, for cases where a compromise is detected mid-lifetime.

**Fail closed, and make the rejection uninformative to an attacker.** When a token is invalid, expired, wrong-audience, or under-scoped, the API should return a generic 401 or 403 without distinguishing 'this user doesn't exist' from 'this user exists but the token is wrong for it.' The same discipline applies to object-level checks: returning 404 for both 'this record doesn't exist' and 'this record exists but isn't yours' avoids confirming the existence of resources an attacker shouldn't be able to enumerate.

**Authorization checks belong on the server, enforced independently at each layer that can act on the request.** A gateway that authenticates transport and forwards a bearer token is not itself an authorization decision point; the auth service that validates the token and the resource server that owns the underlying data must each independently enforce their portion of the check, rather than trusting that an earlier hop already handled it.

07

Requirements

  • A documented list of every API endpoint, the scope or role each requires, and whether it accepts a resource identifier that must be checked for ownership.
  • An authorization server (or equivalent token issuer) that supports short-lived access tokens with issuer, audience, expiry, and scope claims.
  • Server-side logging sufficient to detect repeated authorization denials against varying resource identifiers, without recording token values.
08

Procedure

  • List every endpoint and classify it: does it require authentication only, function-level authorization (a role or scope check), object-level authorization (a per-resource ownership check), or both.
  • For each endpoint requiring object-level authorization, confirm the server re-derives ownership from the authenticated subject and the requested resource identifier on every request — not from a cached decision, a client-supplied flag, or the mere presence of a valid token.
  • For each endpoint requiring function-level authorization, confirm the check is enforced server-side against the token's scopes or roles, independent of what a client's interface chooses to display.
  • Trace a token from issuance to the resource server: confirm issuer, audience, expiry, and signature are validated at every hop capable of acting on the request, not only at the network edge.
  • Confirm token lifetime is short and that a revocation mechanism exists for the window before natural expiry.
  • Confirm both authentication and authorization failures return generic responses that do not distinguish 'invalid credential' from 'valid credential, wrong resource' or reveal whether a given identifier exists.
  • Record the result of each check with supporting evidence (which requests were traced, which responses were observed) rather than marking an item passed because a control appeared configured.
09

Validation

  • For a sample of object-scoped endpoints, confirm that a request using another subject's resource identifier is denied rather than returning that subject's data.
  • For a sample of scope-restricted endpoints, confirm that a token with insufficient scope is denied server-side, independent of client-side UI restrictions.
  • Confirm an expired or wrong-audience token is rejected at every validating hop, not only at the first one encountered.
  • Confirm denial responses for both missing/invalid tokens and unauthorized-object requests are generic and do not leak whether the underlying resource exists.
10

Rollback

  • If a review finds a missing object- or function-level check, treat the finding as internal-source per the publication-safety policy — do not describe the live weakness publicly, and route it to the responsible team for remediation before any public write-up.
  • If a newly added authorization check breaks a legitimate workflow, revert the specific check and its enforcement point rather than disabling authorization broadly, then re-introduce it alongside a corrected data model or claim mapping.
  • If token validation changes cause valid clients to be rejected, confirm the audience/issuer values expected by the change match every legitimate token source before rolling the change out further.
11

Validation or evidence

This guide describes a design pattern and a review procedure; it does not include a reproduced implementation, a captured request/response trace, or a completed assessment of a real system. Its evidence state remains UNVERIFIED — the technical claims are grounded in the cited OWASP and IETF references, not in an exercise performed for this article.

12

Limitations

This guide covers bearer-token API authorization patterns broadly; it does not cover mutual-TLS client authentication, API-key-only schemes without an authorization server, GraphQL-specific authorization concerns, or the authorization-server login flow itself (phishing resistance, MFA, credential-stuffing defenses).

The fictional Meridian Ledger example is illustrative, not a reference architecture. A real API's authorization model must be derived from its own data model and threat model, not copied from this guide's diagram.

Token-binding techniques that tie a token to the specific client that requested it are an active area of practice this guide does not detail; treat their absence here as a gap to research separately, not as evidence they are unnecessary.

13

Defensive recommendations

  • Enforce object-level authorization on every request that names a specific resource, re-checked server-side on each call — never inferred from the fact that a valid token was presented.
  • Enforce function-level authorization against the token's scopes or roles on the server, not only by hiding disallowed actions from a client's UI.
  • Validate issuer, audience, expiry, and signature on every hop that makes a trust decision, not only at the network edge.
  • Keep access tokens short-lived and support explicit revocation for the window between issuance and natural expiry.
  • Return generic, non-distinguishing errors for both authentication and authorization failures so responses don't help an attacker enumerate valid users or resources.
  • Log authorization denials with enough context to detect a pattern of object-ID enumeration, without logging the token itself or other sensitive values.
14

Key takeaways

  • A valid token proves identity and granted scope — it does not by itself prove the bearer is authorized for the specific object or function being requested.
  • Broken object-level authorization and broken function-level authorization are distinct checks that both need explicit, server-side enforcement on every request.
  • Short-lived tokens and revocation reduce exposure time; they are not a substitute for per-request authorization checks.
  • Fail closed with generic error responses, and re-validate trust independently at each layer capable of acting on the request.
15

References

  • OWASP API Security Project (API Security Top 10, 2023 edition): https://owasp.org/www-project-api-security/
  • RFC 6749, The OAuth 2.0 Authorization Framework: https://www.rfc-editor.org/rfc/rfc6749
  • RFC 6750, The OAuth 2.0 Authorization Framework: Bearer Token Usage: https://www.rfc-editor.org/rfc/rfc6750
  • RFC 7519, JSON Web Token (JWT): https://www.rfc-editor.org/rfc/rfc7519
  • RFC 9068, JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens: https://www.rfc-editor.org/rfc/rfc9068
  • NIST SP 800-63B, Digital Identity Guidelines — Authentication and Lifecycle Management: https://pages.nist.gov/800-63-3/sp800-63b.html