Skip to content

Authentication vs authorization

Every request a user makes raises two questions, and they get answered in order.

Who is this? and what are they allowed to do?

The first is authentication. The second is authorization. Both get shortened to "auth", both start with the same six letters, and they solve completely different problems.

Proving who you are

Authentication answers one question: is this really who they claim to be?

You've met the common methods as a user:

  • A username and password.
  • Signing in with Google or GitHub.
  • Sending an API key or a token with a request.

Each is a way of presenting evidence. The server's job is to decide whether the evidence holds.

Get this wrong and someone can present themselves as another person. That's spoofing, the S in STRIDE, and it's the failure that makes every other protection irrelevant, because the app is now applying the right rules to the wrong person.

JunoProving who you are It helps to think of authentication as the door, and everything after it as what you're allowed to touch once you're inside.

The door doesn't care what you want to do. Its only job is deciding whether you're the person you say you are.

JunoProving who you are The status codes are worth getting right, and the names actively mislead. A 401 is called Unauthorized and means unauthenticated: we don't know who you are, so log in. A 403 is Forbidden and means we know exactly who you are and you still may not do this.

Sending a 403 when the user has no session tells the client to give up, when the correct move was to prompt for a login.

JunoProving who you are The distinction to hold onto is that authentication happens once per session and produces a claim, while authorization happens on every request and consumes it. Most identity bugs are really about how long that claim stays trusted after the facts behind it have changed.

Worth separating a third word that gets folded in. Identification is presenting a claim, authentication is proving it, and accountability is showing afterwards who did what.

STRIDE gives repudiation its own letter precisely because a system can authenticate correctly and still be unable to demonstrate later who performed an action.

Deciding what you can do

Authorization runs after, and only makes sense once identity is settled:

  • A user reading their own data.
  • A user updating their own profile.
  • A teacher reading their students' records.
  • That same request from a student, refused.

Get this wrong and people do things they shouldn't. In STRIDE that's elevation of privilege; in the OWASP Top 10 it's broken access control, which has sat at the top of the list for years.

JunoDeciding what you can do Notice that the same person gets different answers depending on what they're asking for.

Being logged in isn't a single permission. It's the starting point for a separate question asked fresh on every request.

JunoDeciding what you can do The common bug is checking whether someone is logged in and stopping there. A route that loads a record by id and returns it to any authenticated user has authentication and no authorization at all.

The question to ask of every handler that takes an id: does it confirm this user owns this record? When the answer is no, that gap has a name, an insecure direct object reference, and it is the most common form broken access control takes.

JunoDeciding what you can do Where the decision lives matters more than how it's expressed. Checks scattered through handlers drift, and the one route that forgot is invisible until someone finds it.

The patterns that hold up centralise the decision: a policy layer the route asks, or a data layer that cannot return rows the caller has no claim to. Both make "which endpoints check ownership" answerable by reading one place.

A hidden button is not authorization. It's a courtesy to honest users, exactly like browser validation, and the route behind it still answers anyone who calls it directly.

The order they run in

Every request that reaches a protected resource passes through both, in the same sequence:

text
request  ──▶  authentication  ──▶  authorization  ──▶  resource
              (who are you?)      (may you do this?)

Two examples make the shape clear. A user reading their own profile: the request arrives, authentication establishes who they are, authorization confirms the profile belongs to them, access is granted.

An admin reaching an admin-only endpoint: the request arrives, authentication establishes who they are, authorization checks for admin rights, access is granted.

Only one of the four steps differs between those. The identity check is identical, and the permission check is where the two requests part company.

Strong authentication with weak authorization is unsafe. Strong authorization without solid authentication is meaningless.

JunoThe order they run in The order isn't a convention, it's a dependency. You can't decide what someone is allowed to do until you know who they are.

That's why authentication comes first every time, and why a mistake there undoes everything after it.

JunoThe order they run in In an Express app both steps are middleware, and the order in the chain is the order in the diagram. An authorization check placed before the authentication one runs against a user who has not been identified yet.

Ordering middleware is behaviour rather than tidiness, which is worth remembering when a route stops working after somebody rearranges its definition.

JunoThe order they run in Watch for authorization decisions made against a stale identity. A session or token established the claim at login, and the role attached to it can be minutes or hours out of date by the time a request arrives.

Whether that gap is acceptable is the tradeoff underneath every identity model in this section. Server-held sessions can be updated or destroyed instantly. Self-contained tokens cannot, which is why a fired employee's token keeps working until it expires.

That's a design decision rather than an implementation detail, and it's the reason the next few chapters are about where identity lives.

Try it

Four situations. For each one, decide whether the failure is authentication or authorization, and name the STRIDE letter it maps to.

  1. A login form accepts any password for the username admin.
  2. A logged-in customer changes the id in a URL and sees another customer's order.
  3. An API endpoint accepts requests with no token at all.
  4. A support agent can delete accounts, which was only ever meant to be possible for administrators.
Compare your answers
#FailureWhySTRIDE
1AuthenticationThe app cannot tell who is at the keyboard, so anyone can present themselves as adminSpoofing
2AuthorizationThe customer really is who they say; nothing checked whether the order belongs to themElevation of privilege
3AuthenticationNo claim is presented at all, so the endpoint has no idea who is calling. A 401 is the correct responseSpoofing
4AuthorizationThe agent is correctly identified and the permission check is wrong. A 403 is the right refusalElevation of privilege

Number 2 has a name of its own: an insecure direct object reference. In OWASP terms both 2 and 4 are broken access control.

Two and four are the same class of bug wearing different clothes, which is part of why broken access control stays at the top of the OWASP list. Both apps knew exactly who was asking.

Where this goes next

Authentication happens once, at login. Authorization happens on every request afterwards, and needs to know who the user is each time.

Which leaves an awkward gap, because HTTP doesn't remember anything between one request and the next.

How apps remember you is the problem that gap creates, and the three families of answer the rest of this section works through.