Key Takeaways
- Authority is a gem that provides a clean solution for authorizing user actions in a Rails app. It focuses on permissions at the model level, allowing for both class-level and instance-level rules to be set using class and instance methods respectively.
- Authority keeps authorization logic DRY by allowing models with the same rules to delegate authorizations to a specified Authorizer class. This structure allows for regular object-oriented programming, with no new syntax to learn.
- Authority provides syntactic sugar for users, enabling statements in an active voice, such as ‘can this user create this resource?’ It also provides methods for controllers to intervene when unauthorized actions are attempted. Authority is compatible with any authentication gem and is ORM-neutral.
- Decide who will speak at the conference?
- Edit the presenter schedule?
- Upload presentation slides?
- Comment on those slides?
- Create playlists of music?
- Make a personal schedule of which talks to see?
Protecting Your Models
Before I show you how Authority works, let’s talk about some of the general ideas. Authority’s notion of permissions in your Rails app is focused on your models; in the example above, these would be presenters, comments, playlists, etc. In some cases, the question is very simple: a conference attendee cannot make any changes to any presenter, period. You can think of that as a “class-level” rule: thePresenter
model is read-only for anyone who isn’t a conference organizer. If an attendee tries to visit the page for editing a presenter’s bio, we don’t have to ask any questions about this particular presenter to know that the action is not allowed.
In other cases, the question is more nuanced: attendees can edit their own personal schedule, but not anyone else’s. You can think of that as an “instance-level” rule: to know whether a conference attendee can edit a schedule, you have to look at that schedule instance and see who it belongs to.
Using Authority, you’d use class methods, like def self.updatable_by?(user)
to set class-level rules, and instance methods, like def deletable_by?(user)
, to set instance-level rules.
But where should those methods be written?
Keeping Your Permissions DRY
Obviously, different models have different rules, so you might think that authorization methods should go on the models themselves. But it’s likely that some of your models share rules: anyone who can edit aPresenter
can also edit the PresenterSchedule
. If that’s true, it would be nice to keep that DRY: let the Presenter
model and the PresenterSchedule
model use the same authorization logic.
Authority accomplishes this by having the model delegate any questions of authorization to a specified Authorizer class. Models with the same rules can point to the same Authorizer.
An Example
To take a simple example from the gem’s README, suppose you two have categories of resources in your app: some for regular users and some for administrators. Using Authority, you’d have two authorizer classes. You might call themBasicAuthorizer
and AdminAuthorizer
.
You could group your models like this:
[gist id=’d38d297e3ed9a3411c3a’]
In this example, the Comment
model’s authorization rules come from BasicAuthorizer
, but Article
and Edition
get theirs from AdminAuthorizer
. You’d call self.authorizer_name =
on each model to set this up.
The AdminAuthorizer
might have a method like this:
[gist id=’0f9d6cb90948117b47fe’]
Anytime a user tries to create an Article
or an Edition
, this method will be called. If the user isn’t an admin, it will return false and the action will be denied.
Any method that isn’t defined on an authorizer will be inherited from Authority::Authorizer
, which will consult a configurable default_strategy
proc. The built-in default strategy simply returns false. This is a whitelisting approach: any action you don’t explicitly allow will be forbidden. But you can supply your own default strategy to do something more nuanced.
So the full lookup chain looks like this:
[gist id=’e577d3ef3b988aa2a1a0′]
Standard Ruby Classes And Methods
The nice thing about this structure is that it’s just regular object-oriented programming. Your authorizers are just classes, so you can modify them any way you like: include modules, change the parent class, metaprogram, etc. In addition, the authorizer’s methods are just plain Ruby methods: there’s no new syntax to learn. Authority makes no assumptions about what logic you’ll need. You can consult a database or a file or a web service to make your decisions; if you use a database, you can use whatever ORM you like. All the logic is up to you; Authority just helps you keep it organized.Syntactic Sugar for Users
So far we’ve seen our authorization methods in the passive voice: is this resource creatable by this user? But it’s nice to be able to say the same thing in an active voice: can this user create this resource? Like several other popular authorization gems, Authority gives you this syntactic sugar.current_user.can_edit?(@article)
is simply a pass-through to @article.editable_by?(current_user)
, which in turn would ask the AdminAuthorizer
.
Since can_edit?
and similar methods are defined on the user object, you can use them anywhere that object is available. A common case would be to show links only to users who should see them:
[gist id=’322a41a42087a7c091d1′]
A Little Magic for Controllers
If you’re using the method shown above to hide links from unauthorized users, most people won’t try anything they shouldn’t be doing. But what if someone manually types the URL to edit a resource that’s forbidden for them? At that point, your controller must intervene. Authority gives you a couple of methods for this:authorize_actions_for(ModelName)
, which checks class-level permissions and will stop the controller method from ever running, and authorize_action_for(@model_instance)
, which checks instance-level permissions from inside a controller method.
In either case, forbidden actions are handled by a controller method that you specify; the default one logs the user’s action and displays a warning.
Check it out
You can get more detail on everything discussed here by looking at the README on Github. I’d also encourage you to read the source code; Authority isn’t a large gem, and the source is well-commented. You may learn something, and of course, reading the code is the first step towards contributing. Happy hacking!Frequently Asked Questions on Authorizing Your Rails App with Authority
How does Authority compare to other Ruby gems for authentication and authorization?
Authority is a flexible, ORM-neutral authorization system for Ruby on Rails applications. Unlike other gems like Devise or CanCanCan, Authority focuses solely on authorization, not authentication. This means it’s designed to control what resources a user can access, not to manage user sessions or passwords. Authority is also unique in its use of ‘authorizers’, which are Ruby classes that encapsulate the rules for a specific model or set of models. This makes it easy to keep your authorization logic organized and DRY.
Can I use Authority with other authentication gems?
Yes, Authority can be used in conjunction with any authentication gem. It’s designed to work seamlessly with gems like Devise or Authlogic, which handle user sessions and passwords. Once a user is authenticated, Authority can then control what resources they can access based on your defined rules.
How do I set up Authority in my Rails app?
Setting up Authority in your Rails app involves a few steps. First, you’ll need to add the gem to your Gemfile and run bundle install
. Then, you’ll need to generate an initializer file with rails generate authority:install
. This file will contain some default configuration options, which you can customize to suit your needs. Finally, you’ll need to create ‘authorizer’ classes for each of your models, which define the authorization rules for those models.
What are ‘authorizers’ in Authority?
Authorizers’ in Authority are Ruby classes that encapsulate the authorization rules for a specific model or set of models. Each authorizer class includes methods for each action you want to control, like ‘readable_by?’ or ‘updatable_by?’. These methods return a boolean value indicating whether the given user is allowed to perform the action.
How does Authority handle role-based authorization?
Authority handles role-based authorization through its ‘authorizer’ classes. You can define different rules for different user roles within these classes. For example, you might have a ‘PostAuthorizer’ class with a ‘readable_by?’ method that returns true for admins and false for regular users. This would mean that only admins can read posts.
Can I use Authority with non-ActiveRecord models?
Yes, Authority is ORM-neutral, meaning it can be used with any object-relational mapping system, not just ActiveRecord. You can define authorizer classes for any Ruby class, whether it’s an ActiveRecord model, a plain old Ruby object, or a class from another ORM like Sequel or Mongoid.
How does Authority handle nested resources?
Authority handles nested resources through its ‘authorizer’ classes. You can define rules for parent-child relationships within these classes. For example, you might have a ‘CommentAuthorizer’ class with a ‘creatable_by?’ method that checks whether the given user is the author of the parent post.
Can I use Authority to control access to controller actions?
Yes, Authority can be used to control access to controller actions. You can use the ‘authorize_actions_for’ method in your controllers to specify which authorizer class should be used for each action. This allows you to centralize your authorization logic in your authorizer classes, rather than scattering it throughout your controllers.
How does Authority handle exceptions?
Authority raises a ‘SecurityViolation’ exception whenever a user tries to perform an action they’re not authorized for. You can rescue this exception in your controllers and handle it however you like, such as by redirecting the user to a ‘403 Forbidden’ page.
Can I customize the error messages in Authority?
Yes, Authority allows you to customize the error messages it displays when a user tries to perform an unauthorized action. You can do this by overriding the ‘message’ method in your authorizer classes. This method should return a string that will be displayed to the user.
Nathan is a former journalist stumbled into programming in 2007. He lives in Charlotte, NC with his lovely wife. He has also been a part-time singer/songwriter: see nathanlongmusic.com.