Roles and Permissions
The shop API’s requirements give merchants and shoppers different permissions. The merchant can create products, change prices, adjust stock, and delete products. Shoppers can browse products, manage their own carts, and place orders.
A permission allows an action on a resource. Creating a product is one permission. Reading your own cart is another.
The simplest design is to give each user their own list of permissions. But users who do the same work need the same permissions. Every shopper needs to browse products and manage a cart. If we store the list separately for each shopper, then a policy change means updating every shopper’s list. If we forget to update one shopper’s list, that shopper keeps the old permissions.
Instead, we group permissions into roles and assign roles to users. A shopper has the permissions of the shopper role because they hold that role. This is called role-based access control, often shortened to RBAC.
The shop API already has two roles in its requirements:
| Role | Resource | Permitted actions |
|---|---|---|
| Merchant | Products | Create, update, adjust stock, delete |
| Shopper | Products | List and read |
| Shopper | Carts | Create; read and edit their own |
| Shopper | Orders | Create from their own cart; read their own |
If the policy for shoppers changes, we change the shopper role’s permissions once. Every user who holds that role gets the change.
There are two assignments here: permissions to roles, and roles to users. In a small application, the permissions for each role can be defined in the code, while each user’s role assignments are stored in the database.
Multiple roles
A user can hold more than one role if their work requires it. For example, recall HopPress, the publishing system where authors write posts, editors review them, and administrators assign roles. Someone who writes posts and also reviews other people’s work needs both the author and editor roles. That user will have the permissions of the author role on their own posts and the permissions of the editor role on the posts they review.
Roles do not necessarily form a hierarchy. You might assume that the administrator can do everything an editor or author can do. In our HopPress design, that is not the case. The administrator role permits assigning roles. It does not grant access to unpublished posts. The application’s policy determines what each role permits. That said, many applications do design their roles as a hierarchy, where a higher role inherits the permissions of the roles below it.
Least privilege
Suppose the merchant hires someone to receive deliveries and update stock. The quickest way to set them up is to give them the merchant role. That role can adjust stock, so it covers the work.
It also lets them create products, change prices, and delete products, which the work does not need. If they send a mistaken request, or if someone steals their credentials, a request made with their credentials can use those permissions. So instead we add a warehouse role with one permission: adjust stock. Then if they make a mistake, or someone steals their credentials, the only thing a request through that role can change is stock counts.
Giving users only the permissions needed for their work is called the principle of least privilege. A request can do only what the caller’s permissions allow. So if a role holds fewer permissions, a mistaken or deliberately harmful request can do less through it. When deciding whether to grant a permission, start with the task the user needs to perform, and add only what that task requires.
Deny by default
Look again at the permission table. It lists what each role may do. Any action the table does not list is refused. This is called deny by default.
The other choice is to allow any action unless a rule forbids it. Then the table would have to list what each role may not do. The warehouse role would need a rule forbidding creating products, another forbidding changing prices, another forbidding reading carts, and a new rule for every operation we add later. Under deny by default, the warehouse role needs one permission, adjust stock.
The shorter list is not the reason to prefer deny by default. A role that needs most operations would have a long list of permissions and a short list of prohibitions. The reason we prefer deny by default is what happens when a rule is missing. Under allow by default, a missing rule grants the action to the role. No request fails, so nobody notices. The shop API keeps running with the wrong permissions until someone happens to spot the action that should have been refused. Under deny by default, a missing permission refuses the action. The person who needed it tells us, and we add the permission. So a mistake under deny by default is easier to notice, and it does less harm than a mistake under allow by default.
Least privilege and deny by default are standard authorization practices. OWASP’s authorization guidance discusses both.