Authentication checks who you are. Authorisation decides what you are allowed to do. They sound alike and often run in the same request, but mixing them up is behind some of the most common security bugs in web apps.
Authentication: proving who you are
Authentication is the step where a system confirms that you are the person, device or service you claim to be. You make a claim ('I am ana@example.com') and back it up with evidence the system can check.
That evidence usually falls into three kinds, often called factors:
- Something you know: a password, a PIN, the answer to a security question.
- Something you have: a phone that receives a code, a hardware security key, a passkey stored on your device.
- Something you are: a fingerprint or a face scan.
Multi-factor authentication (MFA) asks for two or more different kinds. A stolen password alone is then not enough, because the attacker also needs your phone or your fingerprint.
Authentication happens once, at sign-in, and its result is remembered. After checking your password, the server gives your browser a session cookie or a token. Every later request carries that cookie or token, so the server can recognise you without asking for the password again. When the session expires or you sign out, you are a stranger again.
Authorisation: what you are allowed to do
Authorisation starts where authentication ends. The system now knows who you are, and it has to decide whether this particular action is allowed for you: reading this file, editing that order, opening the admin page.
Being signed in is not permission to do everything. A support agent and a customer can both sign in to the same app, yet only one of them should be able to refund an order. Authorisation is the set of rules that makes that difference.
Those rules are usually built from a few common models:
- Roles (role-based access control, or RBAC): users get roles such as 'viewer', 'editor' or 'admin', and each role carries a set of permissions. Simple to reason about, and the most common starting point.
- Ownership: you can edit your own profile and your own orders, but nobody else's. This check depends on the specific record, not just on who you are.
- Attributes (attribute-based access control, or ABAC): rules that combine details about the user, the resource and the situation, such as 'managers can approve expenses under a set amount for their own team'.
Real apps usually mix them: a role decides which pages you can open, and an ownership check decides which records on those pages are yours.
Why the order matters
Authentication always comes first. You can't decide what someone may do until you know who they are. Here is how a typical API handles a request:
The two failures have different HTTP status codes, and the names are confusing:
- 401 Unauthorized actually means 'not authenticated'. The server doesn't know who you are, so signing in (or sending a valid token) might fix it.
- 403 Forbidden means 'authenticated, but not allowed'. The server knows exactly who you are, and the answer is still no. Signing in again won't help.
The 401 name is a historical accident in the HTTP specification. Read it as 'unauthenticated' and the two codes make sense.
A worked example: an orders API
Here is a small Express app with both checks written as middleware. The first finds out who is calling; the second checks a role.
function requireUser(req, res, next) {
const user = sessions.get(req.cookies.sid);
if (!user) {
return res.status(401).end(); // who are you?
}
req.user = user;
next();
}
function requireRole(role) {
return (req, res, next) => {
if (!req.user.roles.includes(role)) {
return res.status(403).end(); // not allowed
}
next();
};
}Routes then combine them. Any signed-in user may view an order, but only if it is theirs. Only admins may delete users.
app.get("/orders/:id", requireUser, async (req, res) => {
const order = await db.orders.find(req.params.id);
if (!order || order.ownerId !== req.user.id) {
return res.status(404).end();
}
res.json(order);
});
app.delete(
"/users/:id",
requireUser,
requireRole("admin"),
deleteUser
);Two details are worth noticing. First, requireUser always runs before requireRole, because the role check needs req.user. Second, the order route answers someone else's order with 404 rather than 403. Both are defensible: 403 is more honest, while 404 avoids confirming that an order with that id exists at all. Pick one and use it consistently.
Permissions can change
Your identity stays the same for the whole session, but your permissions don't have to. An admin can grant you a new role, a subscription can expire, a manager can approve access you asked for earlier. The next request should reflect that.
This is why authorisation is best checked on every request, against current data, rather than decided once at sign-in. If a token carries your roles inside it, those roles are a snapshot. Keep such tokens short-lived, or look permissions up on the server, so a removed permission actually stops working.
The same idea sits behind the principle of least privilege: give each user, service and API key only the permissions it needs right now, and add more when there is a real reason.
Where OAuth and OpenID Connect fit
These two names turn up whenever you add 'Sign in with Google' to an app, and they map neatly onto the two ideas.
- OAuth 2.0 is an authorisation framework. It lets you grant an app limited access to something of yours, such as your calendar, without handing over your password. The app receives an access token that says what it may do.
- OpenID Connect is built on top of OAuth 2.0 and adds authentication. It gives the app an ID token that says who you are.
So an ID token answers 'who is this?' and an access token answers 'what may this caller do?'. Using an access token as proof of identity is a common mistake, because it was never designed to say who the user is.
Common mistakes
- Treating 'signed in' as 'allowed'. An endpoint that checks the session but not the record lets any user read anyone's data by changing an id in the URL. This is called an insecure direct object reference, and broken access control of this kind tops the OWASP Top 10 list of web risks.
- Checking only in the front end. Hiding the delete button from non-admins is good design, but it isn't security. Anyone can send the request directly, so the server must check too.
- Mixing up 401 and 403. Returning 401 to a signed-in user who lacks permission tells the client to sign in again, which can send it into a loop.
- Long-lived permissions in tokens. A token that says 'admin' for a month keeps saying it after the role is removed.
- Rolling your own password handling. Authentication is easy to get subtly wrong. Use a well-tested library or identity provider for sign-in, hashing and MFA.
Key takeaways
- Authentication proves who you are; authorisation decides what you may do.
- Authentication comes first, and authorisation depends on its result.
- 401 means the server doesn't know you; 403 means it knows you and still says no.
- Check permissions on the server, on every request, against current data.
- OpenID Connect handles identity; OAuth 2.0 handles delegated access.