
Adding Authorization to Sinatra with Pundit
Pundit is an authorization framework for ruby. Reading through the documentation it may seem that it is intended specifically for use with Rails but it’s actually framework agnostic and can be added to any ruby application. This tutorial will explain how to add Pundit to Sinatra. If you’ve never worked with Sinatra before you should familiarize yourself on their ‘Getting Started’ page.
Sample App
Like many great ruby tutorials before, this one starts with a simple blog application. Take a look at the app below. There have two models, a User
and a Post
.
It also has two routes:
/posts/:id
: renders a page for reading the post/posts/:id/edit
: renders a form for editing a post
This is a great start for a blog application but has one problem — any signed in user could edit any post. Some code should be added to restrict who is allowed to edit a post. In the simplest case a line could be added that checks that the current user is the same as the post’s author but what if admins can edit any post or an author can grant other authors permission to edit their posts. Putting this code directly in your route would dramatically increase the complexity of your route and make testing each route much more complicated. This is where Pundit can help.
About Pundit
Pundit organizes your application’s permissions logic into objects called policies. Policies are pure-ruby objects which have three main properties
- Policies are named after the model that they grant or deny permission to, with a suffix of
Policy
. In our application, the policy forPost
s would be namedPostPolicy
- Each policy must have an initializer which accepts a user object and the object you want to restrict access to.
- Policies implement a method with a boolean return value for each action a user can perform on the object. For instance, to check permissions to edit a post the
PostPolicy
should implement an#edit?
method.
In the blog application the PostPolicy
should look something like this.
Adding Pundit to Sinatra
First, Pundit will need to be installed and added to Sinatra. Either add it to your Gemfile or install it manually from the terminal.
# Gemfile
gem 'pundit'# Terminal
gem install pundit
Then require Pundit and include it in your application class.
By default, Pundit requires that a method named current_user
be defined wherever authorization checks are performed. This should return whatever object is used to represent the user — in this case it should return an instance of User
. Here’s an example of what a current_user
method might look like for the blog app (assuming the app is using an ActiveRecord-like ORM).
At this point, all of the plumbing is now in place to start checking authorization in routes. Including Pundit in your application class made the authorize
method available in all routes. It accepts two parameters, the object to check authorization for and the action to perform on that object. In the case of the /posts/:id/edit
route the application wants to check if the current user is allowed to edit the post. Updating the edit route, it would look something like this.
The authorize
method does threee things. First, it will use the current_user
method to figure out who is trying to perform the action. Second, it will identify what policy class should be used to check authorization by adding Policy
to the class name of the object passed in. In this case PostPolicy
. Finally it will instantiate a new policy instance with the object and user and call the corresponding action method on the policy. Putting it all together in this case — it will instantiate a PostPolicy
object with current_user
and post
, then call the edit?
method. If that method returns something falsey — meaning the user is not authorized — Pundit will raise a Pundit::NotAuthorizedError
error which you can handle however you like.
Wrapping Up
Pundit is a lightweight authorization tool that is quick and easy to add to Sinatra. This post showed a straightforward approach to getting Pundit set up but there are lots of configuration options which can change how policy classes are looked up, how the user is identified and many other things. If you decide to add Pundit in your app I’d recommend you look through the README.
Hopefully this post was helpful and informative. If you found anything that was confusing or incorrect please let me know. Happy coding!