Planning User Roles for Your Application
When building a web application with authenticated users, it’s important to define which users can perform which actions. One of the most common ways to do this is assigning roles to users. Each role permits certain capabilities, with users only able to perform the actions associated with their specific role. Roles and capabilities should allow overlap between users with similar permissions, while still allowing differentiated levels between users.
In this article, we’ll dig into how to best set up your user roles. You’ll also learn how to ensure these roles are granular enough and how to think about changing user roles over time. Finally, we’ll briefly touch on the benefits of delegating role management to Cerbos so you can focus on your application logic.
Planning User Roles
Let’s imagine we’re designing an application that allows users to vote (yes or no) on different workplace issues. Changes are either made or discarded if they didn’t pass, on the basis of which tally was higher.
When determining what roles we might want for an application like this, it’s helpful to think through all the various workflows of an application and what type of user will be completing them.
For example, we’ve already identified that employees can vote yes or no on issues. In addition, most applications have some sort of administrator role. Admin roles can perform higher-level actions related to data across the application, as well as actions around user management and global settings.
Once we have a rough idea of what roles will exist in our application, we can think about the different resources users with these roles will interact with.
We’ve already identified that the main resource type in our application will be a poll. This poll will need to be creatable (when it’s first put into the system), updateable (if vote items need editing), readable (so users can vote on the vote items) and deletable (once all the votes have been recorded post-poll, or if a poll is created in error).
In addition, we’ll need to have questions. These are the yes or no questions that are part of the poll itself, the global settings data for the whole application and the poll results data (the collection of yes or no votes from users).
Once we have the resources and roles mapped out, we can put them together. In this example, administrators will need permission to do the following:
- Polls: Create/read/update/delete
- Questions: Create/read/update/delete
- Global settings data: Update
- Poll results: No permissions
And employees will need permission to do the following:
- Polls: Read
- Questions: Read
- Global settings data: No permissions
- Poll results: Create/read/update
After mapping these out, we can better identify what’s missing. No roles currently have permission to update settings data, as well as view the poll results. Based on this, we might create a “poll judge” role. This role can view the poll results to tally them (but not vote themselves), and can also update settings data.
For real-world context, the poll judges might be individuals in HR, while the administrators might be vice presidents or C-level individuals. We’d want to keep them in separate roles so they have separate permissions, especially if this system is used to vote on high-impact issues.
Putting It All Together
Now we’ve mapped out our roles and the resources they’ll need to operate, it’s time to put it all together.
To actually implement this application, some of the resources we’ve identified (polls specifically) will need attributes to determine whether they should be accessible to the various roles. For example, polls shouldn’t be visible to the poll judge role unless they have results, meaning employees have cast their votes in that particular poll. So if a poll judge is trying to access an election, your application needs to check whether that election has the
voting_complete attribute or something similar.
Theory is different from practice. Many authorization systems can get complicated, whereby the nice neat roles we defined earlier start to break down. As any application scales, it can make sense to separate authentication and authorization into two systems.
Authorization Management Systems: Buy or Build
As systems become more complex, it’s typical that authorization logic becomes more complex too. When different pieces of the application get too intricately coupled, one system might not be optimal.
Breaking out functionality into pieces is one of the core principles of microservices. But there’s a big difference between building your own microservice and relying on a dedicated access control provider. This is where a tool like Cerbos comes in.
By plugging Cerbos into our previously defined authorization model, we can abstract the authorization layer and instead focus on adding to the business logic of our application. In addition, using a self-hosted, open-source access control provider can enforce sensible constraints on your authorization model and ensure that you’re not leaving any holes in your application’s security logic.
A platform like Cerbos also allows you to test your authorization setup. This is useful in the event your platform does have to evolve; it allows you to avoid breaking something as you progress.
As roles and authorization policies get more complicated, manual testing becomes difficult. You may have given too many permissions to one user, or are denying permissions to someone who should have them. But by defining a test suite for policies, you can ensure your policies are changing on purpose, and not accidentally. Without third-party assistance you’d need to build a variation of this testing framework yourself, only adding to the complexity.
Authorization is crucial to your application; you need a comprehensive plan in place before you even write a line of code. By identifying your roles, resources and how they map together, you’ll be able to build a system that works for you while ensuring your users and applications are secure. As it continues to grow, it’s likely your authentication system will become too complicated to manage internally. A tool like Cerbos.dev can help manage this complexity, and make your application better as a result.