Article presents how to set up a user accounts for 2 types of end-users’ roles and 1 admin role. Users with admin roles have access to Rails_admin dahsboard. Authentication is done by Devise gem and authorization is set up by Cancancan gem.
Let’s assume that Rails project with basic elements is already initialized and works on MySQL database.
- Create User model using Devise gem
- Setup authentication by Devise gem
- Generate views, links and controllers for Devise’s Users
- Create rails_admin dashboard
- Set up authorization using Cancancan
- Give access to rails_admin only for admin (superadmin) users using Cancancan
- Setup mailer for Devise
User accounts – Devise
Open Gemfile and add:
Run bundler and then Devise generator:
It prompts few instructions which we have to do manually. First, add to config/environments/development.rb:
Instead of localhost you can put IP of your machine and you can replace port number 3000 if your app uses different.
Next we should set a root_url in config/routes.rb. Probably you have done it already, but if not, let’s do this now: (config/routes.rb):
The third instructions is about flash messages, which notifies about successful and failed login attempts. We will use for this a toastrgem.
Add to Gemfile:
To application.css and application.js add the following:
To display devise notices and alerts via toastr, add the following code to app/views/layouts/application.rb:
In next step, we can generate User model:
Let’s add a new test account before running migration. Open db/migrate/xxx_devise_create_user.rb and add:
Let’s assume that our application has:
- landing page – available for anyone – in our example app it can be home#index (index action of home controller),
- dashboard – this is available only for logged users – user have to sign in to see this area of app.
Let’s add following to app/controllers/application_controller.rb:
authenticate_user placed in application_controller.rbforces an authentication before each action of any controller. You can verify it now by opening any page of your app in web browser. Instead of the desired page, you should see the login form. But we want that landing page should be available without any authentication. So open landing controller, for example app/controllers/home_controller.rb and add following to skip authenticate_user for index action:
Now you should see your landing page without any authentication.
Do you remember that we’ve created a test user account during migration with following params?
- email: firstname.lastname@example.org
- password: password
You can now try to open another page and enter this to login form to get access to restricted area of your app.
Links and redirections
In this part we will prepare some basic views and links. Let’s start by adding a link to login form to our landing page:
Not signed user will see the Sign in button and would be redirected to login form. Already signed users will see Go to App button which redirects to application dashboard.
You can meet a little problem now. If you are at the landing page, then click on the Sign in button and successfully sign in, you will be redirected back to landing page. But we would like to redirect users directly to dashboard. To do that, we have to overwrite the after_sign_in_path_for method. Open app/controllers/application_controller.rb and add this:
Instead of app_dashboard_index_path you can put path to your page from restricted part of app.
The other links which you should use in your app are presented below. You can use also user_signed_in? method to determine if user is already signed in.
To overwrite views, you have to run following devise command:
The set of views is placed under app/views/users/*. In this article we skip the process of customizing forms, but you can make it now.
Let’s use following command to generate and customize devise controllers:
If you are using custom controllers, you have to set routes in config/routes.rb:
Admin Users – rails_admin gem
Add to Gemfile:
Now you should get access to rails_admin dashboard under /admin in web browser.
You can customize rails_admin gem in config/initializers/rails_admin.rb. To use devise for authentication purpose, add following:
Authorization – CanCanCan
We use CanCanCan gem to restrict access to some parts of app for users with specific roles. Add to Gemfile:
And run bundler. Next, use following command to genrate ability class:
In file app/models/ability.rb, you can define abilities. But first, we have to set users different roles. It’s up to you how you want to define roles. We will add to user’s model a three boolean columns for three roles: superadmin, supervisor, user. Let’s start by generating migration:
$rails generate devise:install
toastr['<%= type %>']('<%= f %>');
$rails generate devise user
# Initialize first account:
$rails generate devise:views users
$rails generate devise:controllers users
## == Devise ==
by Daniel Kehoe
Last updated 16 September 2014
How to control access in a Rails application. An overview of Rails authorization, including role-based authorization, with a comparison of the CanCan and Pundit gems.
This article offers an overview of role-based authorization in Rails. The RailsApps project provides example applications and tutorials demonstrating authorization:
You can create the example applications in a few minutes using Rails Composer.
What is the RailsApps Project?
This is an article from the RailsApps project. The RailsApps project provides example applications that developers use as starter apps. Hundreds of developers use the apps, report problems as they arise, and propose solutions. Rails changes frequently; each application is known to work and serves as your personal “reference implementation.” Support for the project comes from subscribers. If this article is helpful, please join the RailsApps project.
Originally, everyone was anonymous on the web. Browsers requested web pages without identifying the user. In 1997, cookies were introduced to the web to keep track of user sessions, and soon applications were developed that allowed users to create accounts, and sign in to their accounts to initiate sessions. The features that allow users to create accounts (and edit or delete their profiles) are called user management features. Allowing users to sign in and identify themselves is called authentication. Typically, we request an email address and a password to authenticate the user, so we can be sure whoever is signing in is the same person who created the account.
User management and authentication are not core features of Rails but it is easy to add authentication and user management to a Rails application, either by writing the code or adding a gem. If you would like your users to sign in with an account they’ve already established on a popular site such as Twitter or Facebook, you can use the OmniAuth gem. If you’d like visitors to register and sign in with an email address and password, you can use the Devise gem. Both OmniAuth and Devise are robust and full-featured, so most developers use the gems, rather than implementing authentication features themselves. The RailsApps project offers an OmniAuth Tutorial and a Devise Tutorial to get you started.
It’s important to distinguish authentication, which identifies a user, from authorization, which controls what a user is allowed to do. In this article, we look at ways to implement authorization, anticipating that you’ve already added user management and authentication.
Almost every web application needs an authorization system, if there are parts of the website that are restricted to some users. Most websites set access restrictions based on roles; that is, users are grouped by privilege. The web application checks the user’s role to determine if access is allowed. We call this role-based authorization.
In the simplest implementation, we check if a user has a specific role (such as administrator) and either allow access or redirect with an “Access Denied” message. Roles are attributes associated with a user account, and often implemented in a User model. We’ll look at ways to implement roles, but first let’s consider situations where role-based authorization is not suitable.
Alternatives to Role-Based Authorization
Role-based authorization is suitable for simple applications without complex access rules. A big advantage is easy conceptualization; it is easy to imagine personas, each with different (but uniform) privileges. If all you need are role-based rules, use them.
You may encounter complex applications where role-based authorization is inadequate. In these cases, authorization is often based on matching requested activities with a database of privileges. For example, imagine an application that is used across a university to record and report student grades. A student can see his or her own grades for any class; a teaching assistant can enter a grade but not change it after the course ends but only for students in their own section; a professor can enter or change a grade for any student in the class until the next semester begins; the department chairperson can view but not change grades for any student enrolled in a department course; the registrar of records can view or change any grade for any student ever enrolled. Whew! In a real university, the requirements are even more complex, I’m sure. Not only do roles overlap (a professor may also be a department chairperson) but privileges are finer-grained than roles. A user with the role of professor should only have grade-changing privileges for the students in his or her course and it is impractical to create a new role for every new course every semester. This is a use case for building access rules based on permissions attached to activities, not roles.
If you’re building an application with this level of complexity, seek help from experienced developers. You’ll be treading in the territory of large enterprises where initiatives such as User-Managed Access hold sway. This tutorial doesn’t cover such complexity. We’ll focus on the majority of applications where role-based authorization is optimal.
Implementing Role-Based Authorization
Simple role-based authorization requires:
- attributes for roles, typically in a User model
- access rules added to controller actions, restricting access to prohibited pages
- methods to check roles in view templates, displaying content conditionally
In an application with simple access restrictions, you can add authorization with a few lines of hand-crafted code. You’ll need to add a role attribute to a User model. You’ll use helper methods to construct conditional statements for access control in Rails controllers. And you can use the same helper methods to conditionally display content in views.
Many developers use the Pundit or CanCan gems (or its successor, CanCanCan). These gems help organize and centralize access rules in complex applications, keeping controllers “skinny.” As a framework, Rails allows you to add as much complexity to a controller as you wish. However, the Rails community has come to a consensus that complexity in controllers is a not a best practice. Authorization quickly adds complexity to controllers, which is why developers use Pundit or CanCan.
Given the advice, “Keep your controllers skinny,” some developers attempt to implement access rules as methods in a model. Access rules don’t belong in a model, given that a model is best used for retrieving and manipulating data, and not for logic that controls program flow through the application. Rather than build “fat models” with complex access rules for program flow, developers look for ways to keep both models and controllers free from excess authorization code. Pundit or CanCan are options.
You can use the Pundit gem to keep your controllers skinny. Pundit is an authorization system that uses simple Ruby objects for access rules. Pundit uses a folder named app/policies/ containing plain Ruby objects that implement access rules. Pundit is well-suited to the service-oriented architecture that is popular for large Rails applications, emphasizing object-oriented design with discrete Ruby objects providing specialized services.
Pundit policy objects are often described as POROs, or “plain old Ruby objects.” PORO simply means that a Pundit policy object doesn’t inherit from other classes or include code mixed in from elsewhere. In contrast, a Rails model that inherits from Active Record is not a PORO; the model inherits behavior that is defined in a parent class. Because Pundit policy objects are POROs, the code is simple and easy to understand.
CanCan was a popular gem for authorization developed by Ryan Bates (best known for RailsCasts) and abandoned prior to the release of Rails 4.0. Due to its popularity, the community-based CanCanCan project maintains an updated version of CanCan. CanCan provides a DSL (domain-specific language) that isolates all authorization logic in a single Ability class.
CanCan or Pundit?
Before starting a project, developers often want to know what is best, CanCan or Pundit?
As an application grows in complexity, the CanCan Ability class can grow unwieldy. Also, every authorization request requires evaluation of the full CanCan Ability class, adding performance overhead. In its favor, CanCan is popular and well known among Rails developers. If you inherit a project that uses CanCan, you can upgrade to CanCanCan with minimal disruption. Many developers continue to be happy using CanCan.
Pundit also offers the advantage of segregating access rules into a central location, keeping controllers skinny. With Pundit the central location is not a single Ability file. Instead, it is a folder named app/policies/ containing plain Ruby objects that implement access rules. Adding authorization to a controller action requires one line of code that calls a helper method. Pundit uses meta-programming magic to instantiate a policy object that matches an access rule to the controller action. Pundit policy objects are lightweight, adding authorization logic without as much overhead as CanCan. If you wish, you can create a single Pundit policy object for use with all your controllers. More often, developers create a policy object that corresponds with a specific model (for example, a UserPolicy class to match a User model) and will use the policy object to control access for actions in a specific controller (for example, a Users controller).
I prefer Pundit when implementing authorization in a complex application. You can read the Rails and Pundit Tutorial to learn how to use Pundit.
For simple applications, CanCan or Pundit are not necessary and you can use simple role-based authorization without any extra authorization gems. The Role-Based Authorization Tutorial goes into detail.
Neither Pundit or CanCan implement roles. With either gem, or hand-rolled authorization approaches, you’ll need to implement roles, either by adding attributes to a User Model or adding a gem to manage roles. Rails has no convention for implementing roles. There is a wide range of approaches which we’ll survey here.
Just as Rails has no convention for implementing roles, there is no convention established in the framework for a “user.” Developers are free to create a model for an Account, a Member, a Profile, or anything else that meets their requirements. In most cases, developers create a User model, which is a practice we follow in the RailsApps example applications and tutorials.
Single or Multiple Roles
Before you decide which approach you’ll use to implement roles, consider whether your users will each have a single role, or if you will need to assign multiple roles to a single user. Your implementation of roles will be different depending on whether you need a single role or multiple roles.
Let’s consider some examples. If a user can either be an ordinary user, or an administrator, and nothing else, each user has a single role. If a user can join with a bronze, silver, or gold plan, or be an administrator, you’ll only need one role per user. With a single role per user, privileges can be cumulative. You can create access rules so gold users get all the privileges of bronze or silver users (plus more). A single role per user is all you need for many web applications.
If privileges are not cumulative, you may need multiple roles per user. Consider concertgoers. Some may pay extra to sit close to the stage; there’s a “seating” role with values of “close” or “far.” Some may win a contest for a backstage pass; whether they sit close or far, each user will be assigned a value of “access” or “no access” for the “backstage” role. Each user will have multiple roles. Take the time to map out your system of privileges before deciding how you’ll implement roles.
The simplest use case is a user who can be either an administrator or an ordinary user. You can add a boolean attribute to the User model to indicate whether a user is an administrator or not. Then you can check the role with a simple method such as .
This approach has drawbacks when you need multiple roles. You could add another boolean attribute to indicate if a user has a premium plan, but as soon as you add more plans, the approach gets unwieldy, as you’ll need to add a separate attribute to the model for every anticipated authorization level.
For a user who can have only a single role, you could add a attribute to the User model, and set a string representing a privilege level such as “admin,” “gold,” “silver,” or “bronze.” This approach requires only one column in the User data table. You may need some supporting code in the User model that makes sure only pre-defined roles can be used. You can check the role with . With a little extra code, you can implement methods in the User model such as .
You may encounter several limitations with this approach. First, though you can easily add new roles, you can’t easily rename roles once you’ve got registered users (you’d have to change many records in the database). Second, a user cannot have multiple roles; only one role is possible for each user. Finally, this approach requires extra code in the model to implement convenience methods such as . A better approach is Enum roles, described below.
A model attribute can encode a role using a bitmask function. Instead of representing the role as a string, the role attribute can take an integer value. The integer itself means nothing; instead, by decoding the integer as a binary number, each bit represents a role. This is more compact than creating a separate database column for each role.
In the early days of computing, when machine memory was limited and code had to be compact, bitmasks were commonly used to store configuration settings or other data. Today bitmasks are a clever trick that is best avoided. To implement bitmasks to encode roles, you’ll have to add complex methods to your User model (or use the bitmask_attributes gem). There is also no way to set up database indexes or simple queries to retrieve encoded roles. It’s the worst kind of hack; there is no performance benefit and your code becomes much less readable. There are better alternatives.
If your application requires that users have more than one role, a Role model provides the most flexible implementation. The User model and Role model will have a many-to-many association, so a user can have multiple roles and a role can be assigned to multiple users. You’ll need two database tables, one for the User model and another for the Role model. Additionally, to implement the many-to-many association, your database will need an intermediate “join” table named roles_users. Your models will implement the association using the has_and_belongs_to_many association:
An alternative approach uses the has_many :through. This requires an intermediate class such as Assignment. Unless you need to interact with the intermediate object, has_and_belongs_to_many is more appropriate.
With a Role model, each user can be assigned multiple roles. You’ll be able to construct access rules with conditions such as . If you want convenience methods such as , you’ll need extra code in the User model.
I won’t show you the code you need to implement roles using the Role model because there are gems that provide this functionality. You don’t have to implement it yourself.
Royce or Rolify Gems
If your user needs multiple roles, for example, options to allow balcony seating plus a backstage pass, consider adding a gem that adds a roles table. You can implement it yourself, but a gem is more convenient.
Martin Nash’s Royce provides a simple and robust implementation for multiple roles. The Royce gem provides a generator that creates a migration to create a roles table, a users_roles join table, and appropriate database indexes. To add roles to the User model, simply add a method to the model, specifying which roles are available:royce_roles %w[ user vip admin ]
Royce provides a full set of convenience methods without any extra coding, so you can use methods such as:
- – to assign an admin role to a user
- – to determine if a user is an administrator
- – to remove a role
As an alternative to Royce, you can use Florent Monbillard’s Rolify gem to add multiple roles to an application. Like Royce, it provides a generator to set up tables and indexes. Rolify is more complex than Royce. It lets you apply roles to resource classes or instances. For example, for a discussion forum, a user could be a moderator for some forums but not others. Rolify is well-documented on its wiki.
Both Royce and Rolify are convenient and well-tested, so there is little reason to implement your own Role model, if your application requires users with more than one role.
Most applications don’t need to assign more than one role to a user. In many applications, a single role for each user is sufficient, so you don’t need to use the Rolify gem. Instead you can use a feature of Active Record, Enum, introduced in Rails 4.1. Enums are the simplest way to add roles to a User model, with advantages over all the approaches described above.
An enum, or enumerated type, is stored in the database as an integer but represented in code as a string. Enums give us all the functionality we need to implement user roles. We can define the names of the roles, and if necessary, change the names as needed (the integer values stored with each user record remain unchanged). Active Record will restrict the assignment of the attribute to a collection of predefined values, so we don’t have to add any code to restrict the names of the roles to a defined set. Best of all, enums come with a set of convenience methods that allow us to directly query the role without any extra code. For an enum attribute named , with the values , , and , we can use these methods:
- – list all roles
- – make the user an administrator
- – query if the user is an administrator
- – find out the user’s role
- – obtain an array of all users with the admin role
- – we can’t set invalid roles
Active Record automatically gives you convenience methods to assign and query any role. For example, in a User model, if you have:
You’ll automatically get the following methods anywhere you’ve instantiated the user object:
- – assign the user’s role
- – query the user’s role
Active Record enums make it easy to add role-based authorization to a Rails application.
The following code samples are taken from the rails-devise-roles example application on GitHub. The Role-Based Authorization Tutorial explains how to build the complete application.
Thanks to the power of the Active Record enum attribute, we need only a few lines of code in the User model to provide a complete roles implementation for role-based authorization. Here is an example of a User model with the code needed to implement roles:class User < ActiveRecord::Base enum role: [:user, :vip, :admin] after_initialize :set_default_role, :if => :new_record? def set_default_role self.role ||= :user end # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable, :validatable end
The method sets the attribute with a set of predefined role values: , , and . Refer to the documentation for Enum to see other options for setting role values. For example, you can use a hash to explicitly map enum values to integers:
Before you can use a User model with these roles, you’ll need to run a migration to add a new field to the Users table in the database:$ rails generate migration AddRoleToUsers role:integer
The migration will look like this:class AddRoleToUsers < ActiveRecord::Migration def change add_column :users, :role, :integer end end
You’ll access roles by name, such as , , or , but thanks to Active Record enum, the values are stored as integers in the Users table. The values that correspond to each integer are defined in the User class. Whether you use CanCan, Pundit, or simple role-based authorization, you can set up your roles in the User model using an Active Record enum attribute.
User Controller With Simple Role-Based Authorization
The following code samples are taken from the rails-devise-roles example application on GitHub. The Role-Based Authorization Tutorial provides additional details.
Here is an example of a User controller with authorization checks:class UsersController < ApplicationController before_filter :authenticate_user! before_filter :admin_only, :except => :show def index @users = User.all end def show @user = User.find(params[:id]) unless current_user.admin? unless @user == current_user redirect_to :back, :alert => "Access denied." end end end def update @user = User.find(params[:id]) if @user.update_attributes(secure_params) redirect_to users_path, :notice => "User updated." else redirect_to users_path, :alert => "Unable to update user." end end def destroy user = User.find(params[:id]) user.destroy redirect_to users_path, :notice => "User deleted." end private def admin_only unless current_user.admin? redirect_to :back, :alert => "Access denied." end end def secure_params params.require(:user).permit(:role) end end
We’ll apply a that calls a private method on all actions except .
The method checks if the current user is an administrator. If the user is not an administrator, the action redirects to the previous page and displays a flash message, “Access denied.”
The action displays a page with a list of users. We only want administrators to have access to the Users#index page. We want the and actions to only be used by administrators.
The action displays a user’s profile page, with details about the user. We want administrators to have access to any user’s profile. And we want a user to have access to their own profile, but not the profile of any other user.
Views With Simple Role-Based Authorization
The following code samples are taken from the rails-devise-roles example application on GitHub. The Role-Based Authorization Tutorial provides additional details.
Here’s an example of a view file that uses role-based authorization to display different messages to an ordinary user, VIP, or administrator:<% if user_signed_in? %> <% case current_user.role %> <% when 'user' %> <h3>Welcome</h3> <% when 'vip' %> <h3>Welcome, VIP</h3> <% when 'admin' %> <h3>Welcome, Administrator</h3> <% end %> <% else %> <h3>Welcome</h3> <% end %> <p><%= link_to 'Users:', users_path %> <%= User.count %> registered</p>
The Active Record Enum gives us a method we can use to determine the name of the role. We use to find the user’s role. Depending on the role, we display a different welcome message. You can imagine that view templates can quickly become cumbersome if we try to accommodate a large number of roles or complex access rules. If your view templates become complex, use partials to reduce the code complexity, or use Pundit to implement access control logic.
User Controller With Pundit
The following code samples are taken from the rails-devise-pundit example application on GitHub. The Rails and Pundit Tutorial provides additional details.
Here is an example of a User controller that uses methods supplied by Pundit:class UsersController < ApplicationController before_filter :authenticate_user! after_action :verify_authorized def index @users = User.all authorize User end def show @user = User.find(params[:id]) authorize @user end def update @user = User.find(params[:id]) authorize @user if @user.update_attributes(secure_params) redirect_to users_path, :notice => "User updated." else redirect_to users_path, :alert => "Unable to update user." end end def destroy user = User.find(params[:id]) authorize user user.destroy redirect_to users_path, :notice => "User deleted." end private def secure_params params.require(:user).permit(:role) end end
The statement isn’t strictly necessary. It is a second line of defense, offered by Pundit, that ensures you haven’t forgotten to confirm authorization in every controller action where you want it. If you include and a controller action isn’t protected with the method, the user will see an exception when the unprotected action is called.
The action is protected with Pundit using the method call. We add the keyword to any controller action that requires authorization for access. The method takes an argument that tells Pundit where to find access rules. The helper method finds a UserPolicy class and instantiates it, passing the object and either the User class or an instance of the User model, and calling an method to return true or false.
Here’s an example of the corresponding Pundit policy object:class UserPolicy attr_reader :current_user, :model def initialize(current_user, model) @current_user = current_user @user = model end def index? @current_user.admin? end def show? @current_user.admin? or @current_user == @user end def update? @current_user.admin? end def destroy? return false if @current_user == @user @current_user.admin? end end
The Pundit documentation recommends placing policy objects in the app/policies folder. Notice the class definition . It doesn’t inherit from any parent class. It is a plain old Ruby object, which means there are no inherited methods other than what is found in any simple Ruby object. We have to implement everything we need. That’s a benefit; there is no hidden API or domain specific language to learn. Fortunately, the boilerplate needed to implement a policy object is very simple, so we don’t have to add much code to turn a simple Ruby object into a policy object.
The name of the class must correspond to an existing model class (one that inherits from ActiveRecord or ActiveModel). The name of the model class is combined with “Policy” to form the class name. This allows Pundit to find and instantiate the class from the method.
You’ll need to define a method that corresponds to each controller action that requires authorization. Use the name of the controller action combined with a (question mark) character. By convention in Ruby, method names ending in question marks are expected to return true or false. We’ve been calling these methods “access rules,” which is descriptive of their function. There is nothing special about these methods. As long as the method has the name of a controller action followed by a question mark, and returns a boolean, it serves as an access rule for Pundit authorization.
This is a simple example of a Pundit policy object. You’ll have to judge for yourself whether your application is sufficiently complex to warrant use of Pundit. If you anticipate your application will grow in complexity, it is a good idea to use Pundit. Pundit has a steeper learning curve than simple role-based authorization; the Rails and Pundit Tutorial explains Pundit in depth.
Authorization is a requirement for many Rails applications. Role-based authorization is easy to conceptualize and can be added to a User model using an Active Record Enum attribute (use the Royce or Rolify gems if access is predicated on more than one assigned role). Simple role-based authorization may be all you need. If your controller gets overly complex, switch to Pundit to manage authorization.