Signing in is only part of protecting a customer's data. Suppose a customer signs in to a bookstore and opens a private reading list. The API validates the credential, looks up the list ID, and returns the books. Everything appears to work until the customer changes the ID in the URL and receives another customer's private list.
The credential check succeeded. The API still needed to decide whether that customer could read that particular list. Authentication and authorization answer separate questions, and a protected request usually needs both.
This chapter explains the distinction, how the checks fit into request handling, and how to communicate failures without exposing private data.
The examples use a fictional bookstore's JSON API over HTTPS. Customers own private reading lists; support staff can read lists only when the bookstore assigns them to the relevant customer and cannot modify them. These are application policies, not rules HTTP imposes.
Authentication verifies evidence associated with a claimed identity. The result is a trusted representation of the caller, which developers often call a principal. A principal might represent a customer, a support agent, an application, or a background service.
Authorization checks whether the caller has permission to perform the requested action. Its answer depends on the principal, the action, the target resource, and any context the application's policy requires. A policy is a set of rules the API uses to make that decision.
For the bookstore, authentication might establish that a request represents customer cus_104. Authorization then checks whether cus_104 may read list_801.
The same authenticated customer can receive different authorization decisions for different requests. The API lets customers read their own lists, but denies access to another customer's private list and blocks price edits, even though prices are publicly visible.
The diagram shows the separate results each check produces:
A successful authentication result supplies an input to authorization. It does not settle which resources the principal may access.
A credential is evidence a caller presents for verification, such as a session identifier, an access token, or a client certificate. A session identifier lets the server find established session state. An access token represents a grant of access that an API can validate. The verification procedure depends on the credential mechanism.
The important boundary is between data the caller supplies and facts the server has verified. A request body containing "customer_id":"cus_104" is a claim. A header named X-User-Id is also a claim when any external caller can set it. Neither establishes identity by itself.
A login typically verifies credentials and establishes a session or issues credentials for subsequent use. Each protected API request still needs a valid authentication context. The server cannot rely on the fact that a login screen appeared earlier in the browser.
In the HTTP examples, the client sends an access token using Authorization: Bearer .... Anyone who possesses a bearer token can use it, so it needs protection in transit and storage. Every token these examples show is a placeholder. The header name does not mean the server has already made its application permission decision.
A trusted identity may describe software rather than a person. If a nightly inventory importer authenticates as inventory_importer, the API has established the service identity. It has not established which employee, if any, caused that request. When an application acts for a user, preserve the distinction between the application and the represented user wherever the policy needs both.
For the reading-list endpoint, suppose credential validation yields this internal context:
This is an application-defined internal structure, not a token format or a body the client submits. The handler can use principal_id as trusted input because the authentication layer established it.
The flawed implementation in the introduction performed a global check: any authenticated customer could reach the reading-list handler. It then loaded whichever list ID appeared in the URL. That allowed access across customer accounts.
A correct decision needs the relationship between the principal and the selected resource. Under this bookstore's policy, a customer can read a private list only when its stored owner matches the authenticated customer. Changing the path changes the requested resource, so the server must evaluate access to that new resource.
A long, unpredictable identifier can make guessing harder. The permission check must still hold if the caller learns the ID through a copied link or another source.
For list_801, which cus_104 owns, the bookstore applies these rules:
Access to a resource also does not imply permission to perform every action on it. The support agent's read access is deliberately narrower than the owner's access. The API must evaluate the requested operation, including any fields that carry separate permissions.
For example, allowing a customer to rename a list should not allow them to submit an arbitrary owner_id and transfer it to another account. In this API, ownership comes from trusted server state and is not writable through the rename operation. Field validation enforces that input contract; authorization separately establishes whether the caller may rename the selected list at all.
Collection endpoints need the same policy. GET /reading-lists must select lists visible to the caller before producing the response. Returning every customer's lists and asking the frontend to hide the others has already disclosed the data. Counts and pagination metadata must follow the same visibility rules.
The animation below shows why checking who owns an invoice matters even when the caller is signed in.
Customer cus_104 requests their own list. The API verifies the token, establishes the customer principal, and checks ownership before returning a representation.
The examples use HTTP/1.1 over HTTPS. Response lengths count the exact single-line JSON bodies these examples show, without a trailing newline. Cache-Control: no-store is this API's policy for private responses.
The response is small, but the decision depends on information outside the request: the authenticated principal and the stored owner. The server must not accept the caller's assertion that they own the list.
For this customer-only read path, one implementation can combine lookup and ownership enforcement in a database query. The following SQL is illustrative; parameter binding happens through the database library:
Here, requested_list_id comes from the path, while authenticated_customer_id comes from the trusted principal. A missing row means no list is available through this customer's access path. The separate support path needs its assignment policy and cannot reuse this owner-only query as its complete authorization logic.
The sequence makes the dependencies explicit:
The API accesses stored facts to decide what it can disclose. Authorization therefore does not always happen entirely before database access. It must happen before returning protected data or performing a protected effect.
Clients need enough information to respond to a failure. The bookstore distinguishes unusable credentials from denied operations, while concealing other customers' private lists.
401 Unauthorized means the request lacks valid authentication credentials for the target resource. A server generating 401 must include an applicable WWW-Authenticate challenge.
A request without credentials receives this response:
The challenge identifies the authentication scheme. For an expired bearer token, the response can identify an invalid token:
For an unauthenticated request with no authentication information, bearer-token guidance recommends omitting the error attribute. The two responses follow that distinction. The client needs usable credentials before repeating the protected request.
403 Forbidden means the server understands the request and refuses it. Insufficient permission after authentication is a common case, though HTTP does not restrict 403 to authenticated callers.
An assigned support agent can read list_801 but cannot rename it:
The body identifies a restriction the caller may know. Signing in again with the same permissions will not resolve it. The API checks modification permission before changing the name.
A blank name would be a separate validation failure for an authorized owner, which the API reports as 422 under its input policy. For this support request, the API denies modification regardless of the proposed name. Avoid returning protected field details to a caller who cannot perform the operation.
HTTP permits 404 Not Found when the server wishes to hide a forbidden resource's existence. This bookstore uses that option when a customer requests another customer's private list.
The bookstore returns the same status, body, and cache policy for an absent list. It does not reveal the owner's identity in an error message. Status consistency alone cannot prevent every information leak; other endpoints and response timing also deserve attention when existence is sensitive.
The empty and Problem Details responses above are deliberate choices for this API. They do not require every API to use identical bodies.
Authentication often belongs in shared request-handling code so every protected endpoint receives a consistent principal. Authorization needs facts about the requested operation, which may belong in the application service or data access layer.
If an API gateway validates credentials, the application must trust identity information only through a protected gateway-to-service path. A caller must not be able to bypass the gateway or forge forwarded identity headers. Gateway verification also cannot replace ownership checks that require application data.
Apply permission checks to every access path: individual reads, writes, collection queries, exports, and background work. Hiding a button improves the interface, but clients can construct requests directly.
A practical default is to deny an operation unless a rule grants it. Grant principals only the access their work requires. For example, the inventory importer needs catalog permissions; giving it customer-list access creates an unnecessary opportunity for disclosure.
Permissions can change while credentials remain valid. If the bookstore removes a support assignment, an old authenticated session should not silently preserve that assignment forever. Define how quickly policy changes take effect, especially when caching permission decisions. Authentication validity and authorization freshness are separate concerns.
Finally, if a required permission lookup is unavailable, prevent the protected operation. The response should describe an appropriate service failure, such as 503 for a temporary outage, rather than claim that credentials are invalid or that a known policy denied access. Internally record the principal, action, resource identifier where appropriate, and decision or failure reason. Exclude raw credentials from logs.
Authentication establishes a trusted principal from verified evidence. Authorization decides whether that principal may perform the requested action on the selected resource. A valid credential still needs the permission checks the operation requires.
Use trusted identity and resource facts, enforce access on every request path, and distinguish denied operations from invalid input and service failures. Communicate credential failures with 401 and an authentication challenge, use 403 for disclosed refusals, and apply a consistent concealment policy when private resource existence must remain hidden.