What is Authorization ?
Authorization is a method to permit or restrict different users or user groups by assigning them with different user access levels or privileges. This can be done by assigning every user or group of users with different access levels or user roles, roles such as admin, editor, subscriber, end user, etc. For example; Suppose i have a blogging application, i have set three user roles i.e admin, editor & subscriber. So, in this app the admin can have access to everything. The editors can only add or edit posts, they cannot add or remove users or make any major changes to the app. And the subscribers can only read posts and add comments to the posts. Similarly, just like the example above, we can create different user roles and restrict or permit the users to perform only those tasks that they are granted.
Authentication Vs Authorization
Authorization is not the same as Authentication, new developers often get confused between those two terms. Authentication is a method to grant access to users by verifying the identity of the user with user credentials like username, email, password, etc. Whereas, Authorization is a method to restrict or permit users or group of users to perform only those tasks that they are granted, by assigning different user roles or access levels to the users or group of users.
The Pundit Gem
We can create our authorization methods from scratch. But, to make life easier, there is a rubygem called ‘pundit’ that works like a charm. The pundit gem is a policy based authorization system, which makes everything easier to implement. We can create different policies for different models and permit or restrict any user action based on the types of user roles.
Pundit Github: https://github.com/varvet/pundit
Pundit Docs: https://www.rubydoc.info/gems/pundit
Installing Pundit
To install pundit, Add pundit to your gemfile and run bundler.
#File: Gemfile
gem 'pundit'
bundle install
And then, install pundit using the generator
rails g pundit:install
User Roles
Pundit is now ready to use, But first we need to create different user roles, for that we have to create a new active record migration. Please note that: i have already created my user model using devise, if you don’t know how to use devise check out my tutorial on devise by clicking here
Now create a new active record migration, to add a user role, with column type(integer). And then migrate the database
rails g migration add_role_to_users role:integer
rails db:migrate
The User Model
In our users model, we have to initialize the user roles that we want to be assigned to users. For instance, i have added 4 user roles that can be assigned, i.e; :enduser[role 0], :editor [role 1], :admin[role 2], :superadmin[role 3]. And whenever a new user is added or signed up, the default role of :enduser will be assigned, which can be later changed by the admin.
#File: app/models/user.rb
class User < ApplicationRecord
enum role: [:enduser, :editor, :admin, :superadmin]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :enduser
end
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
The Application Controller
In order for pundit to work, we have to include pundit in our application controller. Also, we have to add a private method, as you can see below; to raise a flash notice and redirect to the root path or any other path, if the user is not authorized. The messages can be customized later, if you want to.
#File: app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pundit
protect_from_forgery with: :exception
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:notice] = "Sorry, You Are Not Authorized To Do This"
redirect_to(request.referrer || root_path)
end
end
The Controller
Now, in our posts controller, we can just add authorize Post
inside any action, to authorize with pundit. For instance in the example below, i added authorize Post
to my create action. So, this way whenever a user tries to create a new record, pundit will check for the user role, assigned to the user and only permit the user, if the user has an authorized permission to perform the action.
#File: app/controllers/posts_controller.rb
class PostsController < ApplicationController
before_action :set_post, only: %i[ show edit update destroy ]
#....Other Methods...
#....Other Methods...
def create
unless signed_in?
redirect_to new_user_session_path
else
authorize Post
@post = current_user.posts.new(post_params)
respond_to do |format|
if @post.save
format.html { redirect_to @post, notice: "Post was successfully created." }
format.json { render :show, status: :created, location: @post }
else
format.html { render :new, status: :unprocessable_entity }
format.json { render json: @post.errors, status: :unprocessable_entity }
end
end
end
end
#....Other Methods...
#....Other Methods...
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :description, :post_data, :user_id)
end
end
Just add authorize Post
i.e; authorize [ModelName]
to any action, where you want pundit to check for the authorization. Like this:;
def action_name
authorize ModelName
#.....if authorized do this.....
#.....else do this......
end
Pundit Policies
So, as i said earlier, pundit is a policy based authorization system, which makes everything much easier. Because, we can make changes or add new authorization methods from one place. As you can see below, our post policy. Pundit checks if the current logged in user is admin? and if so, pundit allows the admin user to create?, update? or destroy? the path. Pundit’s policy files can be found in ‘app/policies/‘
#File: app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
attr_reader :current_user, :post
def initialize(current_user, post)
@current_user = current_user
@post = post
end
def create?
@current_user.admin?
end
def update?
@current_user.admin?
end
def destroy?
@current_user.admin?
end
end
Changing User Roles
If the admin wanted to change the user role of any other user, it can be done with a number of ways. If the admin has access to the database or rails console, he/she can do it from there. Or, an admin panel can be constructed from scratch to perform such actions. Or if you wanted to make it quick and easy, you can use the ‘active admin’ gem.
Active Admin Gem: https://github.com/activeadmin/activeadmin
Final Thoughts
So, that was a tutorial on the basics of authorization with pundit, pundit can also be customized heavily to add your own authentication methods or features, please check pundit documentation on how to do it. Also, we can build our own authorization functionality from scratch, if we wanted to. There are also other authorization gems that we can use, to add such functionalities similar to pundit. With gems like cancancan, rolify, authority, etc. So, that’s all folks, have a nice day.