Authorize Users Based on Roles and Permissions Without a Gem

User Authorization is a common problem and there are tons of Gems for Rails apps out there that promise to take care of it. I’ve been researching how I was going to solve the problem for a few weeks now and came to the conclusion that a Gem isn’t necessary and in fact may even complicate things. I came across a great article on user authorization in Rails this week that describes a solution that I’ve adapted to suit my unique situation. If you’re looking for an easy way to implement user authorization from scratch this might be for you.

The Problem

As usual, I’m going to describe the problem before going into the solution so you don’t have to figure out if this will work for you as you read the code samples. Before I even go into the problem, let’s define our terms. “Authorization” is not the same thing as “Authentication”. User authentication is how you verify the identity of a user trying to access your app. In my case I’ve implemented authentication using a combination of username and password auth, API keys, and access tokens. This allows us to verify the identity of a given user and access their information during the course of a request. Authorization on the other hand is the process of verifying that a user, whose identity is already known, is allowed to perform an action or access a resource within an app. If you’re into RESTful architecture these terms can be quite confusing because of how the HTTP spec itself defines response codes. For some reason the 401 code is called “Unauthorized”. You usually (or should) see this code when trying to access a resource over HTTP with an Authorization header which, again, is misnamed. Really, what the Authorization header and 401 unauthorized header really mean is that the user has not been authenticated. The 403 Forbidden response is the real unauthorized response. Anyway, now that we know what we’re talking about, here’s the problem.

In our case we’ve got an API that authorizes users by exchanging their API keys for a one-time-use access token. Rather than doing a database lookup on each request to verify the authenticity of the token and get the user information, we create an API session (implementation details here) by storing active tokens along with their corresponding user data in Redis. We’re not using Devise, Authlogic, or any other Gems to accomplish our authentication. Users of the official web client will be using their username, password, and their API key (in the background) with Rails’ own has_secure_password taking care of user creation and authentication. API users will be using their API keys and tokens to authenticate. Both auth mechanisms create a session in Redis. We don’t have a current_user method available to us like we get with Devise but we don’t need one.

With most roles/permissions gems you end up having to contort your code to work with their way of doing things. In this implementation things are much simpler, however it comes with a caveat. You might really want to contort your code to work with CanCan or Declarative Authorization or other gems or you may not. The solution I’m about to present is quite simple and allows you to do authorization based on roles and permissions.

What are roles and permissions? To put it simply, a role is a type of user. A user with a role has access to certain functionality (controllers for the most part). Each user, in addition to their role, also has a set of permissions. In my case they’re just a set of number codes. A user may have access to a resource through their role but certain actions may be totally off limits to them. That’s why one would use both roles and permissions at the same time.

The solution

We don’t have a current_user method to get user information from but we don’t need it. In our case, any action that requires authentication passes through an authenticate_with_token method in our ApplicationController. This method validates the token header value and stores the user data we need in a @curent_user hash variable. Our entire strategy depends on having some sort of method or variable from which we can get the user’s role and permissions. The authentication method looks something like this:

1
2
3
4
5
6
7
def authenticate_with_token
  if authenticate_with_http_token { |token, options| verify_token(token) && @token = token }
    @current_user = get_user_by_token(@token).with_indifferent_access # Pulls user data from Redis
  else
    render text: Err.token, status: :unauthorized
  end
end

This gives us the user’s role and permissions as @current_user[:role] and @curent_user[:permissions]. This variable lives through the rest of the request as its set in the ApplicationController.

Now the good part. We’re going to create a new type of class – a Policy class. Each policy will live in app/policies and will be named after the controller’s model they are granting access to (Naming conventions will be explained shortly – they’re important!). Let’s create our first one to allow users to log in and create sessions. The Sessions controller is not backed by a model. It’s a controller meant only for users of the web client to authenticate with a username and password. That said, you can still use policies on any controller class as this solution does not rely on any ORM or persistent storage. Its incredibly flexible too as you’ll see.

So we’ve create a new file in app/policies/session_policy.rb (we use the singular version of the controller name and follow Rails’ naming conventions by CamelCasing the class name in code and using snake_case for the file name). For our Session policy we want to restrict access to the create method only to the api_user role with a permissions level of 2. The api_user is a generic user that all unauthenticated users default to being logged in as (its basically the same as being logged out for practical purposes). This user only has permissions to create a new session and new users but cannot access any user data. So here’s what our first policy looks like:

1
2
3
4
5
6
7
8
9
10
11
12
class SessionPolicy
  attr_reader :role, :permissions

  def initialize(role, permissions)
    @role = role
    @permissions = permissions.to_i
  end

  def create?
    @role == 'api_user' && @permissions == 2
  end
end

Let’s go through this line by line. We first set an attr_reader so we can use the role and permissions variables that get passed to the class inside of it. We initialize it to take a role and permissions variable and set them as instance variables. I’ve added a to_i method to the permissions variable because Redis returns values as strings and we know what we’re getting should be able to be converted to an integer. Then we create our first method, create?, which we can now use to see if the user is authorized to access the create method on the Sessions controller.

In our Sessions controller we would now authorize the user like this:

1
2
3
4
5
# SessionsController
def create
  raise SomeError unless SessionPolicy.new(@current_user[:role], @current_user[:permissions]).create?
   # Do controller stuff here
end

But that’s kind of a lot to be typing into every single controller action. We can DRY this out a bunch. We’re going to have different Policy classes for each controller so we need a way to turn this into a before_action filter. So in our ApplicationController let’s try to dry this out…

1
2
3
def authorized?(role, permissions)
  raise SomeError unless "#{controller_name.humanize.singularize}Policy".constantize.new(role, permissions).public_send(params[:action] + '?')
end

Okay, so we have a long one-liner there. We could split that up even more but I don’t see the point since it is quite clear what the method does after reading it and with proper code documentation there shouldn’t be any maintainability problems. What this method does is exactly takes the requested controller’s name (in this case it will be sessions) then it capitalizes the first letter, singularizes the class name, adds “Policy” to the end of it, instantiates the returned string as a new instance of the class, then tacks on the requested controller action to the end with a question mark.

For example, if a user requested sessions/create from a browser or another HTTP client, this method takes that request and turns it into SessionPolicy.new(role, permissions).create?. Meta programming can be awesome and this example is why. Now as long as you stick to Rails’ naming conventions you can use this method as a before filter application-wide and it will automatically call the correct Policy class and method. It even works for custom named methods. If it has a route, this method will know how to handle it. For each controller you want to use this in, you just create a class in app/policies named YourPolicyNamePolicy with a method for each action in your controller with a ? appended to the end.

Now we need to declare a before_action filter that takes the user’s role and permissions and authorizes them in every controller so we don’t have to call this method on an action by action or controller by controller basis. In my case its simpler to skip_before_action in controllers that don’t require authorization than it is to add the filter to every controller. I found that using this as an application-wide before filter is not ideal. It is best to declare it in each controller you’ll need it in. You may be repeating yourself a little but its a one-liner that goes in most controllers. I find this acceptable, your mileage may vary. You can choose what’s best for you but the filter call will be the same. Our authorized? method is a private method in Application controller. We need to be able to pass it arguments. authenticate_with_token is run first, which sets up our @current_user variable and now we can call authorized like this:

1
before_action { |c| c.send(:authorized?, @current_user[:role], @current_user[:permissions]) }

It’s a little different than most before_action or before_filter calls we usually see because its a private method and requires us to pass it values. Because of this we pass it as a block with c being our controller. This code sends our permissions and role variables to the private authorized? method in our ApplicationController. We don’t need to specify which controller its being sent to – just plain c will do – as the authorized? method is already inherited and Rails knows about it.

So there we have it. A simple, flexible, database agnostic way of authorizing users for controller actions. Each policy can accept and use any number of roles, permissions, or other data you’d want to use. Just because I use roles and permissions doesn’t mean you can’t simply use roles alone or permissions alone. You just have to tell each policy what to expect in the initialize method and add any logic you want to each matching controller action method (create?, update?, etc). Hopefully this gives you another way to think about implementing a user authorization system.

Rails, Web development

« Case-insensitive Finder Methods in Rails OS X Mavericks: Fast »

Comments