Authorization with Pundit
In a Rails app, authorization can be implemented in many ways. You can roll out your own simple authorization by assigning a role
number where role = 1
means regular user, role = 3
means admin, etc.
The above implementation was how the Learn app originally had authorization, but since then, our product has grown a lot and we switched to using a gem called Pundit.
A few nice things about Pundit is that it’s very light weight and uses plain old Ruby objects (PORO). Beyond that, how you implement the authorization is pretty flexible. Pundit also comes with built in helper methods so you can authorize your users easily.
Configuring Pundit
The first step is to include the pundit
gem in your Gemfile
. Afterwards, you need to include the Pundit
module in your ApplicationController
so all that authorization goodness is available in your app.
Next, we need to setup a “policies” folder so go ahead and create one in app/policies
. It really doesn’t matter where we put the policies, but this is the place that’s suggested. More importantly, what are policies…
Policy Objects
Pundit
uses what is called “Policy Objects” to determine a particular user’s authorization on the specific object. For instance, let’s say we have a resource in our app called Organization
. In order to check a user’s authorization for an organization, pundit will look for an OrganizationPolicy
.
So you can imagine the OrganizationPolicy
looks something like this:
Like I mentioned earlier, this is a PORO and doesn’t depend on any inheritance. But you know what might be good is if we create an ApplicationPolicy
that takes care of some of the boilerplate stuff like the initialize
method. That way all of our policy objects can inherit from it:
Determining Authorization
I also mentioned earlier that you can implement your authorization logic how you see fit. So there’s no hard fast rule on the implementation, but to give you an idea, there might be a polymorphic table in our database that determines the authorization level. For instance you might have something like:
Don’t worry too much about where all those ActiveRecord
models came from. What we really care about is that there is now a UserRole
that ties the user, organization, and a role. Now for our OrganizationPolicy
we can do something like:
Now, the #show?
method returns a boolean value based on if the user has the proper record in the UserRole
model.
Authorizing a User
Ok we set up all the policy objects and the proper authorization logic. Now how do we authorize a particular user?
At the core of it, you actually don’t even need the helper methods provided by Pundit
. In your controller, you can easily do:
But that’s meh. You can use a helper method called ‘authorize’ provided by Pundit
to make it more sexy. authorize
takes in the record object you want to authorize and an optional method name that should be called on the policy object:
If the user is not authorized, Pundit
will raise Pundit::NotAuthorizedError
that you can rescue in your ApplicationController
. Another thing that the authorize
method does is that if you don’t pass in the method name, it infers it by the controller action name and tacking on a question mark on it.
What’s cool is that we can use the policy objects without the helper methods so they can be used to authorize users in our API endpoints, our serialized objects, and so on.
There’s much more you can do with Pundit like change the scope for a resource based on the user’s authorization, custom error messages, headless policy objects, etc. I highly recommend checking out this blog post, written by one of team members behind Pundit
.