[RUBY] How to do API-based control with cancancan

Overview

When building a Web system, I think there are relatively many requests to "manage authority for each user."

In Ruby on Rails, you can use a Gem called cancancan to manage permissions, but this Gem manages execute permissions (read / write) on the Model. The basic usage is to do.

This time, there is a requirement that "I want to manage the execution authority of API", and I investigated the method, so I summarized it.

General usage of cancancan (model-based control)

Before getting into the main subject, I will briefly introduce the general usage of cancancan (model-based control). (Almost a Japanese translation of the Gem Readme)

Install Gem

Add the following to your Gemfile.

gem 'cancancan'

Definition of Ability

The privileges granted to users are defined in the ʻAbility class. First, create the ʻAbility class with the following command.

rails g cancan:ability

As an example, control the execute permission of the model Post.

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Post, public: true  # public=Anyone can see the true record

    if user.present?  #Define additional login user privileges
      can :read, Post, user_id: user.id  # user_You can also refer to records whose id is your own

      if user.admin?  #Define additional permissions for the admin user
        can :read, Post  #All records can be referenced
      end
    end
  end
end

cancancan has a rich wiki with detailed definitions. Defining Abilities - cancancan

Confirmation of authority

When checking permissions in a view

<% if can? :read, @post %>
  <%= link_to "View", @post %>
<% end %>

You can check if the user has read permission for the variable @post with can?: read, @post. In the case of the above example, the link is displayed only when you have the reference authority of @ post. For more information on the helpers available in the view, see the following pages on the wiki: Checking Abilities - cancancan

When checking permissions on the controller

As a prerequisite, the method current_user must be able to reference the logged-in user. Pre-install Gem for authentication such as Devise and Authlogic.

def show
  @post = Post.find(params[:id])
  authorize! :read, @post  # current_user@Error if post cannot be referenced
end

If you write load_and_authorize_resource, before_action will be added to load the resource and check the authority according to the controller name. You can use load_and_authorize_resource to prevent the situation where you forgot to write ʻauthorize!` In the action you added later and missed the permission check.

class PostsController < ApplicationController
  load_and_authorize_resource

  def show  # GET /posts/:post_Called when accessing id
    # before_Do the following in action
    # @post = Post.find(params[:post_id])
    # authorize! :show, @post
  end
end

Supplementary information

The following aliases are internally cut in cancancan.

alias_action :index, :show, :to => :read
alias_action :new, :to => :create
alias_action :edit, :to => :update

So, for example, ʻauthorize!: Show, @post and ʻauthorize!: Read, @post will give the same result. cf. Action Aliases - cancancan

load_and_authorize_resource can be divided into load_resource and ʻauthorize_resource. In the above example, @post = Post.find (params [: post_id])part ofload_resource is before_action, and ʻauthorize_resource is ʻauthorize!: Show, @ post part of before_action. `Is added. cf. Authorizing controller actions - cancancan

API-based control

This is the main subject. As an example, suppose you have defined two actions in your Post controller: show and ʻupdate`.

class PostsController < ApplicationController

  def show  # GET /posts/:post_API to call by id
  end

  def update  # PUT /posts/:post_API to call by id
  end
end

Allows the admin user to execute both GET / posts /: post_id and PUT / posts /: post_id, and other users to execute only GET / posts /: post_id.

Definition of Ability

class Ability
  include CanCan::Ability

  def initialize(user)
    can :show, :post  # GET /posts/:post_id can be executed by anyone

    if user.admin?  #Define additional permissions for the admin user
      can :update, :post  # PUT /posts/:post_id can only be executed by admin user
    end
  end
end

Confirmation of authority

The purpose is to control the execution of the API, so check with the controller, not the view.

class PostsController < ApplicationController

  authorize_resource class: false

  def show  # GET /posts/:post_API to run by id
    # before_Do the following in action
    # authorize! :show, :post
  end

  def update  # PUT /posts/:post_API to run by id
    # before_Do the following in action
    # authorize! :update, :post
  end
end

The key is to use ʻauthorize_resource class: false instead of load_and_authorize_resource`.

ʻAuthorize_resource class: false` reason to use

Considering the case of show of PostsController as an example, ʻauthorize_resource seems to add before_action` that works with the following logic. (I didn't follow the source properly, so it may be different exactly)

if @Is a resource assigned to post?
  authorize! :show, @post
elsif 'class: false'Is not specified?
  authorize! :show, Post
else
  authorize! :show, :post
end

If you use load_and_authorize_resource, the resource will be put in @ post, so the permission check of @ post will run. Also, if you do not specify class: false, the model Post permission check will run. That's why we use ʻauthorize_resource class: false`.

in conclusion

I searched online for how to manage API execution privileges, but I couldn't find anything else, so I summarized it this time. If you have any mistakes, please feel free to comment.

I think these requirements will increase with the popularity of microservices. If you find yourself in a similar situation, I would appreciate it if you could refer to it.

Recommended Posts

How to do API-based control with cancancan
How to number (number) with html.erb
How to update with activerecord-import
How to scroll horizontally with ScrollView
How to get started with slim
How to enclose any character with "~"
How to use mssql-tools with alpine
How to get along with Rails
How to start Camunda with Docker
How to adjustTextPosition with iOS Keyboard Extension
How to share files with Docker Toolbox
How to compile Java with VsCode & Ant
[Java] How to compare with equals method
[Android] How to deal with dark themes
How to use BootStrap with Play Framework
How to switch thumbnail images with JavaScript
[Note] How to get started with Rspec
How to do base conversion in Java
To do Stream.distinct with field properties etc.
How to achieve file download with Feign
How to update related models with accepts_nested_attributes_for
How to set JAVA_HOME with Maven appassembler-maven-plugin
How to implement TextInputLayout with validation function
How to handle sign-in errors with devise
[Note] How to use Rails 6 Devise + cancancan
How to delete data with foreign key
How to test private scope with JUnit
How to monitor nginx with docker-compose with datadog
How to deal with Precompiling assets failed.
How to achieve file upload with Feign
How to run Blazor (C #) with Docker
How to build Rails 6 environment with Docker
How to download Oracle JDK 8 rpm with curl
[Java] How to test for null with JUnit
How to mock each case with Mockito 1x
How to use MyBatis2 (iBatis) with Spring Boot 1.4 (Spring 4)
How to save to multiple tables with one input
How to test interrupts during Thread.sleep with JUnit
How to use built-in h2db with spring boot
How to search multiple columns with gem ransack
How to use Java framework with AWS Lambda! ??
How to deploy
[Swift] How to link the app with Firebase
How to create multiple pull-down menus with ActiveHash
How to use Segmented Control and points to note
How to use Java API with lambda expression
How to get started with Eclipse Micro Profile
How to give your image to someone with docker
How to insert all at once with MyBatis
How to monitor SPA site transitions with WKWebView
How to write test code with Basic authentication
[Rails] How to easily implement numbers with pull-down
How to build API with GraphQL and Rails
How to use nfs protocol version 2 with ubuntu 18.04
How to use docker compose with NVIDIA Jetson
How to get resource files out with spring-boot
How to create member variables with JPA Model
How to verify variable items with WireMock's RequestBodyMatching
How to use nginx-ingress-controller with Docker for Mac
[Rails] How to build an environment with Docker
How to avoid exceptions with Java's equals method