Contact Us

Simple authorization in Ruby on Rails apps

Ruby on Rails | October 21, 2020

Here at Elabs, we’ve been using CanCan for authorization in a number of applications. Ryan Bates managed to build an authorization system which is both simple and powerful. A step away from the bloated role based system available at the time, yet more sophisticated than simply tacking on methods on ActiveRecord models.

Over time though we’ve come against a few grievances with CanCan.

And finally: at the time of writing, CanCan has 128 open issues, 28 open pull requests. Important functionality in the gem is broken, and attempts to fix it through pull requests are ignored. The test suite depends on ActiveRecord < 3.1 and won’t even run with later versions of ActiveRecord, unless someone fixes this, we don’t actually know if CanCan works at all with newer versions of AR.

In a recent project we worked on, we were running against bugs in CanCan which forced us to run a forked version, and we were fighting against an ability file which was growing out of control. We decided that we needed a new way to approach the problem.

Back to basics

We really like CanCan’s simple approach. The ability file isolates all authorization logic, and it leaves you free to handle authorization however you want to. You are free to grow your authorization system from a single user role to whatever complexity you need. We were intent on keeping this flexibility.

We wanted something simpler though. Something which we can implement without really needing a library at all. We wanted to have full control over how the authorization system works.

We took inspiration from objectify and Bryan Helmkamp’s excellent blog post 7 Patterns to Refactor Fat ActiveRecord Models among others and pared the whole thing down to creating a plain Ruby class for each domain model.

We call these classes policies and we put them in app/policies. They might look like this:

Using these classes from the controller is fairly easy:

This works quite nicely, but unfortunately it’s a lot more code to write in the controller. Controllers are un-DRY enough as it is; we need to make this easier. It’s simple enough to introduce a helper method for fetching a policy for a given record:

Now we can simplify our create method somewhat.

We can easily wrap this pattern in another method:

And we end up with this:

That looks a lot closer to what we have in CanCan.

This pattern works fine for 6 of the 7 restful actions, but what about #index?

CanCan can automatically construct a query based on the permissions you have specified, as long as the hash based syntax is used, anyway. Unfortunately we’ve found this magic to be error prone and sometimes insufficient. We really want to use scopes for this, but we don’t want those to pollute our model objects. Again, taking inspiration from Bryan’s blog post, we create a class for this:

Usage for this from the index action is also fairly easy:

Again, we can simplify this with a helper method:

Both the policy and policy_scope method are especially useful in views. We can do things like this:

Our views are kept quite nice and DRY, just like with CanCan.

We have bundled up these helpers in a very simple gem we’re calling Pundit. It has a few more tricks up its sleeves, but basically it does exactly what this post outlines. We found that we could replace CanCan with this pattern very effectively. The resulting code is simpler, easier to understand and easier to test.

While we do think that Pundit has been useful to us, there is a bigger takeaway from this. We had a problem, and we threw a large library with a complicated DSL at the problem, and as the old saying goes, we now had two problems. Sometimes the simpler solution is better. Sometimes it makes sense to leverage Ruby, over creating your own mini language.

Spend some time reconsidering the dependencies you have in your application and whether they are actually helping you, or if you’re spending more time fighting them than you’re getting out.

This content was originally published here.