Create a JSON:API with Authorization using Graphiti and CanCanCan

Kyle Barton
2 min readJun 28, 2019

Want to create an api using rails that supports the JSON API spec and has authorization using cancan? Here’s how…

Backstory:

  • CanCan was originally written by Ryan Bates and the community has taken over and extended the project into CanCanCan.
  • There’s a rails gem called Graphiti that is a big help when conforming to the JSON API spec.
  • JSON:API was originally drafted by Yehuda Katz in May 2013. This first draft was extracted from the JSON transport implicitly defined by Ember Data’s REST adapter .

Show Me The Code:

  1. First off, add graphiti and cancancan to your project. I’m going to leave that as an exercise for the reader.
  2. In application_controller.rb define a current_ability method and rescue from authorization errors

3. Create the Ability classes. Cancancan recommends creating an ability class per model. I decided to have my abilities inherit from a base ability to minimize shared permission definitions. I also decided to create a factory that could instantiate the current_ability based on the current controller instance. It looks something like this:

4. Setup Graphiti to perform the authorization:

NOTES:

  1. When relationships are listed in a create request, graphiti currently calls the update callback for each relationship. We check if the model.changed? before checking authorization to ensure that access denied exceptions don’t occur for relationships that don’t allow write access.
  2. If you have a nested ability for a model with json type attributes, you will run into issues with how the base_scope generates the query. accessible_by generates a query that contains DISTINCT "my_table".* . The error looks like this:
ActiveRecord::StatementInvalid:PG::UndefinedFunction: ERROR:  could not identify an equality operator for type jsonLINE 1: SELECT  DISTINCT "my_table".* FROM "my_table...

In other words, it’s possible you’ll run into the error above if your ability looks something like this:

To fix this, you can implement the base_scope on the child class like this:

5. Add some rspec helpers:

--

--