Intermediate · Authentication and authorization · topic 35 of 54 · 30 min
Authorization: who can do what
Explanation
Who you are vs what you may do
Authentication answers "who are you?" (login). Authorization answers "are you allowed to do this?" A logged-in customer is authenticated, but they must still not be authorized to refund other people's orders.
401 or 403?
| Status | Meaning | Example |
|---|---|---|
| 401 Unauthorized | Not logged in, or the login is invalid | No session cookie, expired JWT |
| 403 Forbidden | Logged in, but not allowed | A customer opens the admin refunds page |
| 404 Not Found | Sometimes used instead of 403 to hide that a record exists | Another user's private order |
Role-based access control (RBAC)
In RBAC each user has a role, and each role has a list of permissions. A permission is a named action like "product:update". The code asks "does this role have this permission?" instead of checking user names.
| Permission | customer | staff | admin |
|---|---|---|---|
| product:read | yes | yes | yes |
| product:update | no | yes | yes |
| order:read | own only | yes | yes |
| order:refund | no | no | yes |
// Example only: runs on a server with Node.js and Express
const PERMISSIONS = {
customer: ['product:read', 'order:read'],
staff: ['product:read', 'product:update', 'order:read'],
admin: ['product:read', 'product:update', 'order:read', 'order:refund'],
};
function requirePermission(permission) {
return (req, res, next) => {
if (!req.user) return res.status(401).json({ error: 'Login required' });
const allowed = PERMISSIONS[req.user.role] ?? [];
if (!allowed.includes(permission)) return res.status(403).json({ error: 'Forbidden' });
next();
};
}
app.post('/api/orders/:id/refund', requirePermission('order:refund'), refundOrder);Ownership checks
Roles are not enough. A customer has "order:read", but only for their own orders. So after loading the record, check that order.userId equals the logged-in user's id. Even better, put the owner in the query itself: SELECT * FROM orders WHERE id = ? AND user_id = ?.
Broken access control and IDOR
IDOR (Insecure Direct Object Reference) is when the server trusts an id from the URL without checking ownership. Broken access control sits at number one in the OWASP Top 10 list of web risks.
GET /api/orders/1043 (Asha's own order) -> 200 OK
GET /api/orders/1044 (Asha changes one number) -> 200 OK <- BUG: Ravi's address and phone!
Fixed server:
GET /api/orders/1044 (order belongs to Ravi) -> 403 Forbidden (or 404)- Deny by default: if no rule says yes, the answer is no.
- Least privilege: give each user, staff member and API key only the permissions they need.
- Check on the server, on every request, for every action, including "read".
- Log denied attempts; many 403s from one account is a warning sign.
- Write tests for "user A cannot see user B's data".
Watch the lesson
Authorization: who can do what · 1/6
Logging in tells us who you are. Authorization decides what you may do.
Prefer a video by a teacher? Find free videos on “rbac authorization broken access control idor explained” on YouTube ↗
In real life: Order details on an e-commerce site
An order page URL looks like /orders/1043. If the backend only checks "is someone logged in?", any customer can change the number and read other people's names, phone numbers and addresses. A correct backend loads order 1043 and checks that its user_id is the logged-in user (or that the user is support staff with the right permission). Everyone else gets 403 or 404. Bugs like this have exposed customer data at real companies, which is why security testers try them first.
Practice (0/3 done)
Write can(user, action, resource) that returns true or false. Rules: no user (null) -> false. role "admin" -> everything allowed. role "staff" -> only "product:read", "product:update" and "order:read". role "customer" -> "product:read" always; "order:read" and "order:update" only when resource.ownerId === user.id. Anything else -> false (deny by default).
Write handleGetOrder(req, orders) for GET /api/orders/:id. req is { user, params: { id } } where user may be null and id is a string. Return { status, body }: 401 with { error: "Login required" } if there is no user; 404 with { error: "Not found" } if no order has that id; 403 with { error: "Forbidden" } if the order's ownerId is not the user's id and the user is not an admin; otherwise 200 with the order as body.
Design the roles and permissions for a college library app with students, librarians and an admin. Make a permissions table and list two IDOR risks with how you would block them.
Quick quiz
Read more (free)
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.