【初心者向け】RailsでAPI開発 Part.3


これは何?

【初心者向け】RailsでAPI開発 Part.2の続きです。

今回は認可編になります。


概要


▼ 仕組み

前回認証をやりましたが、それは下記の2番目にあたる箇所です。

実際にクライアントからリクエストが来て複雑なアプリケーションを作っていくには下記のような順番で判別します。


  1. ヘッダーにトークンが含まれてるかどうか

  2. トークンが正しいものかどうか <- 認証

  3. 認証したユーザーがアクセスできるリソースかどうか <- 認可


▼ 使用するもの


  • Pundit


手順


① Punditの準備

まず、PunditをGemに追加してインストールしてください。


terminal

bundle exec rails g pundit:install



② ApplicationControllerの準備

ここで、ヘッダーにトークンが含まれているかどうかの判定や、

含まれている場合に認証できるのかどうかの判定などを行います。

認可については、アクセス不可の場合の記述をこちらで行います。


app/controllers/application_controller.rb

include ActionController::HttpAuthentication::Token::ControllerMethods

include Pundit

rescue_from ActiveRecord::RecordNotFound, with: :no_record
rescue_from Pundit::NotAuthorizedError, with: :no_authorization

def authenticate
if request.headers[:HTTP_AUTHORIZATION] != nil
current_user || no_authentication
else
no_header
end
end

def current_user
authenticate_with_http_token do |token, options|
@current_user = User.find_by(token: token)
end
end

def no_header
render json: { error: 'No Header' }, status: 400
end

def no_authentication
render json: { error: 'No Authentication' }, status: 401
end

def no_authorization
render json: { error: 'No Authorization' }, status: 403
end

def no_record
render json: { error: 'No Record' }, status: 404
end



③ 認可の設計

認証を突破したユーザーの中で、

ユーザー編集や削除は自分だけ(認証したユーザー自身)がアクセスできるように認可を設計します。

つまり、

indexshowアクションは認証を突破した人誰もがアクセス可能ですが、

updatedestroyアクションは認証を突破した人の中でも、

アクセスするリソースに入れる権限を持つ自分自身だけにPunditで制御します。


④ UserPolicyの作成


app/policies/user_policy.rb

class UserPolicy < ApplicationPolicy

def show?
true
end

def update?
record == user
end

def destroy?
record == user
end
end


showメソッドは誰でもアクセスできるようにしてますが、

updatedestroyは認証ユーザーと合致しない限りはアクセスできないようにしてます。


⑤ Policyの呼び出し


app/controllers/v1/user_controller.rb

before_action :authenticate, except: [:create]

before_action :set_user, only: [:show, :update, :destroy]

..

private
def set_user
@user = User.find(params[:id])
authorize @user
end

..


showupdatedestroyを呼び出す、set_userauthorize @userとすれば、

アクセスしようとしているリソースのユーザーと認証ユーザーが合致しているかの判断ができます。


⑥ 確認


showメソッド(認可なし)


  1. トークンなしでアクセス



    1. No Headerエラー



  2. トークンあるけど、存在しないトークン



    1. No Authenticationエラー



  3. 存在するトークンだけど、アクセスするリソースのユーザーが保有しないトークン


    1. 成功



  4. 存在するトークンかつ、アクセスするリソースのユーザーが保有するトークン


    1. 成功



showメソッドの場合は、認可は全て許可しているので認証ユーザーであれば値を返す。


destroyメソッド(認可あり)


  1. トークンなしでアクセス



    1. No Headerエラー



  2. トークンあるけど、存在しないトークン



    1. No Authenticationエラー



  3. 存在するトークンだけど、アクセスするリソースのユーザーが保有しないトークン



    1. No Authorizationエラー



  4. 存在するトークンかつ、アクセスするリソースのユーザーが保有するトークン


    1. 成功



destroyメソッドの場合は、認証ユーザーとリソースのユーザーが同一でないとアクセスできないので、

3の場合はNo Authorizationエラーが発生していればOK。


まとめ

今回はPunditを使って認可を設定してみました。

僕は以前Gemを使わず自前で認可を設計していたことがあるのですが、

Controllerの中身が結構ごちゃごちゃになってしまうので困っていました。

Punditを使うとごちゃごちゃにならずPolicyファイルに綺麗にまとめられるため非常に利便性を感じました。

これで、認証・認可周りはかなりスッキリしたのではないかなと思います。

次回は外部からの攻撃など、セキュリティ周りをしっかりとみていきたいと思います。