We recently deployed a complete reimplementation of the user management and authentication part of our customer web platform. This now includes user authorisation - I think this time we mostly got it right, touch wood.
Authorisation in software applications is tricky to get right. There are always a hundred opinions on how it should be done, in introduces a heap of corner cases that you didn't anticipate and you often end up with something that is much more complex than what you really need.
I have had the chance to implement authorisation for different web applications. Over time I have build up a list of considerations to review before blindly running down the implementation path.
Before I dive into these considerations, lets first define authorisation. Authorisation determines if a particular user has the access rights to a particular resource or functionality. For example, does the user, Siebert, have the access right to create blog posts.
Also consider that authorisation has two parts: enforcement and management. The enforcement part makes sure that Siebert can not create blog posts if he does not have the access right. The management part provides the interface through which an access right can be assigned to Siebert.
Do not confuse authorisation with authentication. Authentication determines if a particular user is who they claim to be. For example, we trust the user is Siebert because he presented the password for Siebert's account.
Do you really need it (right now)?
The first question I ask is "Do you really need authorisation in your application?". Authorisation introduces complexity to the code, testing, user interface and security aspects of your application. This is worth avoiding if the business value is not high enough.
Authorisation is rarely an attractive feature to users, it is mostly annoying as it introduces extra overhead when using the application. Authorisation should only be implemented when the risk of not having authorisation is high and trust between users can not be assumed.
Suppose we're building a blogging platform which allows multiple users to create posts for the same blog. The following authorisation rules might be considered:
- Users can only edit posts that they have created themselves
- All users can create posts but only certain users can publish posts
Whilst both rules sound very sensible I'd beg the question whether they are really a priority requirement for our initial release. Can we let the users manage responsibilities amongst themselves? Can we release the application without authorisation and add it later if this is what our users ask for?
Accountability over authorisation
In many cases it is more sensible to create a system that keeps users accountable for what they have done instead of restricting their access. Authorisation puts limitations on a system and forces the users to adapt their workflow to these restrictions. Accountability gives all users clear visibility on the actions of other users. Users then keep each other accountable and are free to optimise their workflow without any system limitations.
Lets consider how this applies to our blogging platform. How do we allow users to collaborate on writing posts if they can only edit their own posts? What happens if a user wants to edit and republish an already published post if they are not allowed to publish posts? In both these cases the authorisation rules will force the users to adopt a suboptimal workflow. Rather, by making all blogging activities clearly visible to all users they are encouraged to moderate their own process of creating, editing and publishing posts.
Permissions vs. roles
Examples of permissions for our blogging platform might be: create posts, edit posts, delete posts, publish posts and unpublish posts. Examples of roles might be: authors and moderators. Where authors can create, edit and delete posts, and moderators can publish and unpublish posts.
The question is - Do we assign permissions directly to users or do we prefer to assign roles to users? Permissions provides great flexibility but increases complexity, roles provides simplicity but reduces flexibility.
This might seem trivial in our example but it becomes a big consideration in complex systems. When deciding on an role based system, be careful of mapping it to organisational roles that might change, rather map it to functional roles in the actual application.
Also consider that with every additional permission or role, more complexity is potentially introduced to the application. Keep a tight reign on this.
Authorisation has the ability to significantly reduce the user experience if it is not done right. Lets consider authorisation management when creating a new user - It is relatively easy to create an interface to assign roles to a new user. The trick is in communicating what the implication is of assigning a role to a new user. When assigning the moderator role to a user on our blogging platform, it should be clear that this implies the ability to publish blog posts. It sounds obvious but it is easily neglected.
The enforcement of authorisation should not leave the user wondering why they do not have access to a certain functionality which they expected to have access to. On our blogging platform, it should be clear to a user with the author role, that just saving a post would not publish it. Also they should not be left wondering how they are suppose to publish a post.
Also consider the cases where a user might have multiple roles. The interface should not give contradicting information to the user. For example a user with the author and moderator role should not be provided with a reason why they can not publish a post when there is a publish button on the screen. Or even worse, display the publish button but then fail when it is clicked.
Separation of concerns
This mostly applies to enforcing authorisation - You want to avoid decision points scattered through your application logic that checks if the logged in user has the access right to do something. You want to separate the authorisation rules as much as possible from your functional logic. This means you can test you application logic separate from your authorisation rules.
For Ruby on Rails, cancan and declarative_authorization have done a brilliant job of separating these concerns. If you decide not to use these, at least refer to their design for inspiration.
My approach is to first build the functional logic and then apply the authorisation logic on top of that. Less moving parts at a time. For example, we first write the automated tests and implement the logic to publish a blog post. Only when this is done do we apply the authorisation rule that stops a user without the moderator role to not be able to publish a blog post, ensuring that our initial automated tests still pass.
Your objective for implementing authorisation will mostly be security. Remember to consider the familiar security principle - defence in depth. Consider how this might apply to your application. Do you have multiple layers that should each maintain their own authorisation?
Also be careful to think that the interface to you application starts and ends at the browser interface. Not displaying the publish button in not going to stop a malicious user with only the author role from sending a manually crafted request to the url that publishes blog posts.