Authorization advice for Rails 3 and beyond
If you thought you had a lot of options as a Rails developer for adding authentication to your Rails applications, search GitHub or RubyGems.org sometime for role or authorization. As you’ll see, the need to add an authorization layer to applications is a common one with more than one solution. In this post I won’t talk much code, nor will I give you a list of gems—that’s easy enough to create for yourself. Instead, I will talk about the thought processes I go through when adding an authorization layer to a new project.
For the uninitiated: When I talk about _authentication_, I mean adding the ability to let people log into your site. When I talk about _authorization_, I mean defining what they're allowed to do once they've successfully logged in. In other words, authentication determines who you _are_, authorization determines what you can _do_.
Why is authorization important? Rather than try to answer this question myself, let me remind you of last summer’s breach at Citi, when hackers stole more than 200,000 credit card numbers. The approach? Logging in, then changing the account number in the URL. Want to bet some developers lost their jobs over that one?
How much do you need?
Before you invest a lot of time adding a complex authorization layer to your app, assess what’s necessary. How many different types of users will you have? What will those users be able to do (or not do)? A lot of times you or your customer may start a project assuming that you’ll need complex roles, when in reality you’ll only need a handful—and in some cases, a full-blown authorization layer isn’t required for the level of security you need:
- If you’re building a simple content management system or blog, you may be able to get by with just two roles: users, or people who are logging in and manipulating content; and guests, or people who are visiting your site but not logging in. There needs to exist trust between users to not do malicious things to other users’ accounts or content—this obviously would not be sufficient in a lot of situations (like the Citi example above), but may be enough if your goals are simple.
- Extending from the previous example, if you only have two types of user—say, regular users and administrative users—then a simple boolean value in your user model may suffice. Then you can check for extra permissions via something like
current_user.admin? (note: I prefer to use
current_user.is_admin?, only because I like the way it reads). That said, if you feel the urge to just add a boolean for each role a user might have (
is_editor?, etc.), stop yourself immediately and plan to integrate a more robust roles solution.
It’s relatively easy to migrate from either of these scenarios to a more complex one if and when the time comes. In the short term, however, they may provide all the security you need to ship your initial project.
Assuming you’ve walked through the thinking processes I’ve outlined above, and you’ve determined that your current authorization needs are more complex than a simple boolean can handle, then it’s time to peruse GitHub and/or RubyGems.org for a good roles manager. You can also create your own—this is the approach I’ve taken after experimentation with several gems. Making your own roles solution isn’t difficult; this Railscast on embedded associations shows one way to accomplish it. Whether you opt for someone else’s gem or your own code, though, consider these questions:
- To which model should the role belong? In most cases, probably, it’s safe to assume that the user will have assigned roles. However, there are plenty of situations in which the role will actually belong to something associated to the user. For example, say you’ve got users and groups, and users belong to groups through memberships. Some group members can manage the group, others can read content in that group, and non-members can’t access it at all. In this case, the membership has roles, and the user’s roles are thus implied. With that said, make sure the gem you pick to handle roles is model-agnostic. RoleModel is a good example.
- I’ve shied away from approaches that use bitmasks to store a given user’s roles. The aforementioned RoleModel and Railscast episode both do this. I found that as I added or removed roles I had to regenerate the bitmasks for each user. It may be worth revisiting the approach once an application’s roles aren’t in flux and changes to them are more unlikely.
- Finally, I’ve been using my roles approach to add a blocked role. At my day job we build tools to collect data in schools, and when a principal or teacher moves on we want to keep what she or he recorded but not allow them access to a given school’s information. We don’t delete any user or membership information—rather, we block access to that school’s data. (This also entails replacing the usual
destroy functionality in controllers to add the blocked role, versus actually deleting a user or membership).
At this point I’m ready to actually build the authorization layer. If it’s anything more complex than a couple of calls to
current_user.is_admin? I almost always use CanCan. It’s flexible, up-to-date, and thoroughly documented. In the past I used RESTful_ACL, which took a different approach to authorization but got the job done. I featured RESTful_ACL early on in Everyday Rails; consult the archives to learn more.
Finally, if you’re not writing tests for your Rails apps, then building an authorization layer may make you want to finally start. Otherwise you’re looking at some incredibly tedious in-browser testing. No thanks. Most authorization systems you’ll find on GitHub should include instructions on how to test them. In the case of CanCan I test the actual Ability model; I also test to make sure things are working as they should in either a request or controller spec.
That, in a nutshell, is the process I take when adding authorization to an application. While the tools will change over time, I hope this provides a good roadmap for your own work.
blog comments powered by