Authorization is an important aspect to most any application. As a system, it is put in place to determine whether the current user has the permission to perform the requested action. Based on this, it typically happens after a user is authenticated, but before a request is processed.
The important question to ask is, is the user allowed to do what they’re trying to do?
When considering implementing an authorization system in Rails, there are two popular libraries.
The first, Declarative Authorization has been around since 2008. It introduced the idea of a centralized permissions file and a clean DSL for referring to those permissions.
Later, CanCan, was inspired by DeclarativeAuthorization and created by Ryan Bates of Railscasts. It provides an intuitive interface to define your authorization rules and integrates into Rails seamlessly.
They’re both great choices, but let’s look at implementing CanCan.
To get started, add
cancan to the
bundle from the command line.
The Current User
It is conventional to implement a helper method named
current_user in your controllers. It should return an instance of the
User model that is currently active in the session.
CanCan is expecting
current_user to be available for its controller includes to work, which are setup automatically in descendants of
ActionController::Base once the
CanCan gem is required.
To define an application’s authorization rules, we’ll think in terms of abilities. For example, is the
current_user able to update their own information?
Generate the Ability File
As of version 1.5,
CanCan includes a generator to create our
Ability file for Rails 3 applications. It is placed in the
app/models directory and is where all of your ability definitions will live. To execute the generator, run the following from the command line:
CanCan provides a succinct DSL for defining abilities. Let’s dive in and look at a simple example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
CanCan generator will provide the structure for your Ability class. At a basic level then, all you need to do is implement the constructor. Here we can see a few things…
- As is recommended in the
CanCandocumentation, the case where a
- A user can perform all actions (
- Users cannot perform any actions on the
- Users can read instances of the
CanCan does not make any assumptions about whether you will need roles in your application and what they might be. At their most basic, we could implement administrators and non-administrators.
To begin implementing roles, let’s consider a simple
1 2 3 4 5 6 7 8 9 10 11 12 13
As you can see, the
User::Roles array lists the different roles that a
User might be. Since we want to persist a user’s role, we would add a
String field called
role to the
User model via a database migration.
Now, to define the abilities of an
:admin, we should revisit the previously created
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
If a user is an
:admin, they’re allowed to manage any
Article. Otherwise, users can only read articles. By using the simple
User#is? method, we can expressively determine the role of a user.
Once your application’s abilities are defined, they can be checked throughout the app.
can? check uses your ability definitions to determine whether a user action is allowed.
cannot? helper is available and checks whether a user action is NOT allowed.
1 2 3
Load and Authorize Resource
CanCan provides numerous helpers for use at the controller level. One that is particularly useful is
load_and_authorize_resource. Calling this at a class level in a controller will automatically load the model and authorize the requested action.
1 2 3 4 5
Handling Authorization Failure
load_and_authorize_resource manually, an authorization failure will raise a exception. The way to handle these exceptions is by writing a
rescue_from block in your base controller.
1 2 3 4 5 6 7 8 9 10
The exception passed in to the
rescue_from block will contain the data necessary to inform the user of the error.
Dealing with Complexity
As your application grows beyond a few models, ability definitions can
quickly become complex. At first, a solution such as the one described
in the last section of the
CanCan Role Based
wiki page can be useful. Even then, the
ability.rb file can grow
unruly at fast pace.
At a certain point, there are few options to keep ability definitions under control:
- Roll your own solution to split ability definitions in to multiple files by role or model.
- Use a
CanCanextension gem such as cantango or role_up
Writing tests against your ability definitions at the unit level, as
opposed to through functional or integration tests, can be a huge
benefit for your codebase. There are wonderful matchers available for
CanCan. Check out the testing wiki
page for code
Use the Blogger sample application to complete the exercises in this section. See the Setup Instructions for help.
- Follow the instructions above to install CanCan and generate the abilities file.
- Follow the previous Local Authentication tutorial to build authentication using Devise.
- Define a
roleattribute in your users table. Create one admin and one non-admin user.
- Define abilities such that the admin can manage an
Articleand a normal user can only read them (using
- Add checks in the view templates to hide links when the user is not permitted to execute the associated action.
ArticlesControllerand remove any unnecessary code in the actions.