Authentication & Authorization
Authorization with CanCan
Learning Goals
After this tutorial you should be able to:
- Explain how authorization differs from authentication
- Use CanCan to implement authorization helpers in a Rails application
Getting Started
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?
Library Choices
When implementing an authorization system in Rails, there a few choices to consider.
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. Its last update was in March 2013, so the project is stalled and probably isn’t the right choice.
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. The tutorial below will use CanCan as it is the most popular choice, but it too has stalled out in the last year. The community has forked the project to continue merging pull requests and improving the library, now known as CanCanCan
The newcomer is Pundit from our friends at elabs, the same bunch that put together Capybara. It’s worth checking out.
Setup
Let’s try out CanCanCan. To get started, add cancancan
to the Gemfile
.
1
|
|
Then run 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.
Creating Abilities
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:
Terminal
$
|
|
Defining Abilities
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 |
|
The 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
CanCan
documentation, the case where acurrent_user
isnil
is handled. - A user can perform all actions (
:manage
) theArticle
model - Users cannot perform any actions on the
Comment
model. - Users can read instances of the
Tag
model wherebaz.released
istrue
Building Roles
By default, 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 User
model.
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.
Admin Abilities
Now, to define the abilities of an :admin
, we should revisit the previously created Ability
class.
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.
See the CanCan
wiki for more information on Defining Abilities and Role Based Authorization.
Checking Abilities
Once your application’s abilities are defined, they can be checked throughout the app.
Can?
The can?
check uses your ability definitions to determine whether a user action is allowed.
1
|
|
Cannot?
Alternatively, the 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
When using 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.
Usage Patterns
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
Authorization
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
CanCan
extension gem such as cantango or role_up
Testing Abilities
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
RSpec
in CanCan
. Check out the testing wiki
page for code
examples.
Exercises
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
role
attribute in your users table. Create one admin and one non-admin user. - Define abilities such that the admin can manage an
Article
and a normal user can only read them (usingindex
andshow
). - Add checks in the view templates to hide links when the user is not permitted to execute the associated action.
- Use
load_and_authorize_resource
in theArticlesController
and remove any unnecessary code in the actions.