You are currently viewing Authentication vs. Authorization: Checking Access to Each Resource

Authentication vs. Authorization: Checking Access to Each Resource

A signed-in user requests /invoices/482. The server knows who they are, but does it know whether invoice 482 is theirs to view? Authentication establishes the identity of a person or software client. Authorization decides whether that identity may perform a specific action on a specific resource. A working login does not prevent an application from exposing another user’s data.

Two decisions, made at different points

Authentication asks, “Which principal is making this request, and how was that established?” A principal might be a user account, a background service, or an API client. A successful password login can establish an account identity and create a session. It does not grant access to every record in the application.

Authorization combines that identity with an action and a resource. “Can account 17 read invoice 482?” is more useful than “Is account 17 logged in?” The answer may depend on ownership, organization membership, an assigned role, the invoice’s status, or several of those facts together. A server may also allow certain actions without a signed-in identity, such as reading a public help page. That, too, is a deliberate access decision.

Question Example Typical failure
Authentication: who is calling? Does this session identify account 17? An invalid session is accepted
Authorization: what may they do? May account 17 download invoice 482? A valid user downloads another account’s invoice

Separating the checks makes code easier to reason about. The authentication layer establishes a trusted request principal; the authorization layer evaluates that principal against the requested operation and current resource data. Neither should assume the other has answered its question.

Login and resource access require separate decisions

How an application establishes identity

In a typical web login, the user submits a username and password over a protected connection. The server looks up the account and verifies the password against its stored hash. Don’t store passwords in plaintext or rely on a fast, general-purpose hash alone. Use a password-hashing function designed to make guessing expensive, with a unique salt for each password. Once verification succeeds, the application establishes a session or issues another credential for subsequent requests.

A session cookie usually holds an opaque identifier linked to server-side state. A bearer token carries or references information the server can validate. Either can be misused if someone obtains a usable copy: they may be able to act as its holder until it expires or is revoked. Set browser session cookies with Secure, HttpOnly, and an appropriate SameSite policy. Rotate the session identifier when an anonymous session becomes an authenticated one, and account for logout, expiration, and revocation.

Authentication may instead involve an external identity provider. The application still needs to validate the provider’s response under the chosen protocol and determine which local account it represents. A login button returning a profile is not enough. Tokens have intended issuers, audiences, lifetimes, and signing requirements; one service cannot assume a token issued for another is valid for it.

Multi-factor authentication can strengthen login, but it cannot replace authorization. Even a user who has completed a second factor needs a resource-level permission check before downloading a document.

Credentials are not all interchangeable

  • Password: a secret presented to prove knowledge at login; avoid treating it as a reusable API credential.
  • Session cookie: a browser-carried identifier commonly used to preserve a signed-in session.
  • API token: a credential a client presents to an API, often limited by scope and expiration.
  • Service identity: an identity for an application component, which should receive only the permissions its task requires.

A credential’s format does not determine its privileges. An API token identifying a service still needs explicit permission to read or change a resource.

Design permissions around actions and resources

Start with verbs and objects, not a vague “admin” flag. In an invoice application, actions might include invoice:read, invoice:create, invoice:approve, and invoice:delete. Reading might be permitted for the invoice owner and members of the same organization’s billing team. Approval might require both a billing approver role and an invoice awaiting approval. State those conditions alongside the action so a role name does not hide the business rule.

Role-based access control assigns permissions to roles such as reader, billing clerk, and approver. It suits fairly stable responsibilities. Attribute-based checks consider properties of the principal, resource, action, or context, such as organization ID, ownership, or record status. An application may use both: a role permits approval in principle, while a resource check prevents an approver from approving another organization’s invoice.

Roles do not replace tenant boundaries. A billing clerk at one organization should not gain access to another organization’s records. Include organization membership in the decision even when the role is correct, and do not feed an organization ID supplied by the browser into a broad query without checking the principal’s membership.

Apply least privilege to people and services. A report-generating service that reads approved invoices does not need permission to delete them. Deny an action unless a policy explicitly permits it, and document intentionally public resources so an unguarded endpoint is not mistaken for an oversight.

Object-level checks belong on the server

Hiding an “Edit” button helps the interface reflect a user’s permissions, but it is not a security control. Clients can send requests without using the interface. Nor should changing an invoice ID in a request be enough to access another customer’s record. The server must load the target record and check permission against it before returning data or making a change.

For GET /invoices/482, the server might authenticate the request, load invoice 482, and check that the principal belongs to its organization and has read permission. Only then should it serialize the invoice. A denied request can use a generic response to avoid revealing unnecessary details about whether a private invoice exists.

Invoice access varies by role and organization

Put checks where they cannot be skipped

Authentication middleware can consistently establish a principal and reject invalid credentials. Authorization often needs information the middleware does not have, particularly when the decision depends on a database record. A practical place for the permission check is the application service handling the operation: after it obtains the required resource facts, but before it exposes data or commits a change.

For reads, do not fetch every matching record and filter the results in the browser. Restrict database queries to records the principal may access, then apply any additional per-record rules on the server. For writes, check permission close to the update. If the rule depends on mutable state, a transaction or conditional update can verify that the required state still holds when the write happens.

Keep reusable policy rules in one place, with explicit inputs. canApproveInvoice(principal, invoice) says more than isAllowed(user) and gives tests a clear target for organization boundaries, roles, and invoice status. But checking a policy once at page load does not authorize every later API operation.

Asynchronous work needs the same care. A user may be allowed to request a report, while a background worker retrieves the data later. Decide whether that worker acts under a narrowly scoped service identity, the requesting user’s authority, or a documented combination. Record the requesting principal and validate the work’s scope before processing; a queue message must not become an unchecked instruction to fetch arbitrary records.

Failure responses and audit records

Missing authentication and insufficient permission are different conditions. HTTP APIs commonly return 401 Unauthorized when valid authentication is missing and 403 Forbidden when an identified caller lacks permission. Despite its confusing historical name, 401 helps a client distinguish “login needed” from “access denied.” Sometimes 404 Not Found is appropriate to avoid revealing that a private resource exists. Choose a consistent response policy without skipping the access check.

Record useful security events: failed logins, session revocations, permission changes, and denied access to sensitive operations. When known, the audit entry should include the principal, action, relevant resource identifier, result, and timestamp. Never log passwords, complete session cookies, or bearer tokens. Logs help with troubleshooting and review; they are not credential storage.

Test the boundaries with a small permission matrix

A test that signs in as an owner and reads an owned invoice says little about isolation. Use two users and two organizations in a local development environment, and exercise direct API requests as well as interface flows. Use only accounts and resources created for your own tests.

  1. Create an owner in organization A, a billing clerk in A, and a user in organization B.
  2. Create an invoice in A and another in B, with distinct test data.
  3. Verify that permitted users can perform their intended read and write actions.
  4. Verify that a signed-in user from B cannot read or alter A’s invoice by supplying its ID.
  5. Verify that a user who may read an invoice cannot approve it without the required permission and status.
  6. Repeat relevant checks after a role is removed, a session expires, or an invoice changes status.

Call the same server operations used in production; checking which buttons appear is not enough. Test missing credentials, invalid credentials, and a valid identity with insufficient permissions separately. If roles or token claims are cached, test how quickly a permission change takes effect and decide what delay is acceptable. Removing a role in the database may not immediately invalidate a long-lived token.

For each new endpoint, write down a concrete rule: “A billing approver in the invoice’s organization may approve an invoice whose status is pending.” Then test an approver from a different organization and an invoice that is already approved. Both should be denied, even though the caller can sign in.