EXPLAINER

Authentication versus Authorization

Authentication answers who is calling. Authorization answers what they may do. A valid login is not permission to read every row. Check both, on the server, for every action that matters.

Short answer: Authentication is who you are. Authorization is what you may do. A session, an OIDC login or an API key answers the first. A check in your server answers the second. Hiding a button is not authorization.

What it is

Authentication (authn) establishes identity. A password and a second factor, a passkey, an OIDC ID token, or an API key are different ways to answer "who is this caller?" After that, your app usually creates a session so the next request is not a full login. Authorization (authz) decides "may this identity do this action to this object?" Examples: only the author may edit the document; only an admin may refund; only a member of the tenant may see the tenant's files.

The words are similar and the bugs are not. Logging in is not the same as being allowed. The short forms authn and authz exist so people can say which one they mean in a design review.

Why and when it is used

Every multi-user system needs both. Authentication without authorization is how any logged-in user reads every other user's data by changing an id in the URL. Authorization without authentication is a policy with nobody to apply it to, except for truly public resources. Single-user local tools can skip both.

Do the authorization check on the server (Frontend and Backend). The UI can hide buttons so people are not offered actions that will fail. If the UI is the only check, an ordinary HTTP call bypasses it. Do not build a permission matrix so abstract that nobody can say what a new endpoint should require. Start with ownership and a small set of roles.

How it works

A request arrives. Middleware authenticates it: valid session cookie, valid bearer token, valid API key. It attaches an identity (user id, tenant id, client id) or rejects with 401. The handler then authorises: load the object, compare its owner or tenant to the identity, or ask a policy. Failure is 403. 401 means "I do not know you". 403 means "I know you and the answer is no". Mixing them up confuses clients and sometimes leaks whether an id exists; pick a policy deliberately for private objects.

Roles (admin, member) are coarse. Ownership (created_by) is specific. Attributes ("department matches the record") are another step. Put the check in one place per resource so a new endpoint cannot forget it. Tests should include a second user who must be denied. OAuth 2.0 scopes are a form of authorization for an API, and they still are not your row-level rules.

Technologies and dependencies

A session or token mechanism (JSON Web Tokens, cookies), a user store, and policy in code or in a dedicated engine if the rules are genuinely complex. Microsoft Entra ID groups can feed roles. They should not be copied into every service in an ad hoc way without a refresh story. API Authentication: Keys, Sessions and OAuth covers credentials at the door. This page is the distinction after the door opens.

How to get started

  1. For one resource (a note), require a session. Return 401 if there is none.
  2. Store owner_id on the note. On read and delete, require owner_id to match the session user. Return 403 otherwise.
  3. Write a test that user A cannot read user B's note by guessing the id.
  4. Add an admin role that may read all notes, and a test for it. Do not implement admin as "skip all checks" sprinkled in handlers.
  5. Hide the delete button in the UI only after the server test exists.
  6. Log denials with the user id and the object id, not with the object's private contents.

When you add a new route, the code review question is "where is the authz check?" If the answer is "the user is logged in", that is not an answer.

Cautions and trade-offs

Checking authentication in a middleware and assuming authorization happened is the recurring outage-of-privacy. IDOR bugs (insecure direct object references) are this page in incident form. Multi-tenant apps must include tenant id in the check, not only user id; the same user id in two tenants is two people or one person with two contexts.

Role checks that trust a role string from the client are authentication theatre. The role comes from your database or from a validated token you issued. And do not leak secrets through error text ("denied, but the document title is ..."). Finally, background jobs have identity too. A worker that uses a god credential and a user-supplied id without a check will export the wrong tenant's data the first time a queue message is wrong.