[Ruby on Rails]sorceryによる認証 – (5)APIでの認証 #2 コントローラ
はじめに
前回に引き続き、sorceryによるAPI認証についてです。今回はコントローラとその周辺のソースコードについて書いていきたいと思います。各コントローラの概要については、前回の記事を参考にしてください。
ソースコード
ではソースコードです。ソースコードは全てを、解説はポイントとなるところのみを書いていきます。
UsersController
ユーザを登録、参照、削除するためのコントローラです。ソースは以下のようになります。
app/controllers/api/v1/users_controller.rb
module Api module V1 class UsersController < Api::V1::ApplicationBaseController before_action :set_user, only: [:show, :edit, :update, :destroy] skip_before_filter :require_valid_token, only: :create # GET /users.json def index @users = User.all end # GET /users/1.json def show if !@user respond_to do |format| format.json { render nothing: true, status: :not_found } end end end # POST /users.json def create respond_to do |format| @user = User.new(user_params) if @user.save format.json { render nothing: true, status: :created } else format.json { render nothing: true, status: :bad_request } end end end # PATCH/PUT /users/1.json def update respond_to do |format| if @user.update(user_params) format.json { render json: @user } else format.json { render json: @user.errors, status: :unprocessable_entity } end end end # DELETE /users/1.json def destroy @user.destroy respond_to do |format| format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find_by_id(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def user_params params.require(:user).permit(:email, :name, :password, :password_confirmation) end end end end
APIのバージョンを付与するため、Api::V1の中に作成しています。またユーザを作成するcreateアクション以外では、有効なAccessTokenかをチェックするrequire_valid_tokenメソッドを呼び出せるようにしています(このrequire_valid_tokenについては後述します)。
index、show、create、update、destroyの各アクションについては、特に複雑なことは行っていないと思います。ユーザ情報を返却するアクション(index、show、update)については、結果をJSONで返却しています。以下がそのJSONの定義となるjbuilderです。
app/views/api/v1/users/index.json.jbuilder
json.array!(@users) do |user| json.id user.id json.email user.email json.name user.name end
app/views/api/v1/users/show.json.jbuilder、app/views/api/v1/users/update.json.jbuilder
json.extract! @user, :id, :email, :name
これらも特に複雑なことは行っていなくて、オブジェクト(@users、@user)を受け取ってJSONに出力しているだけです。
UserSessionsController
ログイン、ログアウトを行うためのコントローラです。ソースは以下のようになります。
app/controllers/api/v1/user_sessions_controller.rb
module Api module V1 class UserSessionsController < Api::V1::ApplicationBaseController skip_before_filter :require_valid_token, only: :create def create if @user = login(login_user[:email], login_user[:password]) api_key = @user.activate @access_token = api_key.access_token else respond_to do |format| format.json { render nothing: true, status: :not_found } end end end def destroy access_token = request.headers[:HTTP_ACCESS_TOKEN] api_key = ApiKey.find_by_access_token(access_token) if api_key user = User.find(api_key.user_id) user.inactivate respond_to do |format| format.json { render nothing: true, status: :ok } end end end private def login_user params[:user] end end end end
createアクションでは、パラメータで渡されてきたメールアドレス・パスワードを元にログインを行います。ここで呼び出している「login」メソッドは、sorceryにて用意されているものです。ログインできた場合、Userモデルのactivateメソッドを呼び出し、api_keysテーブルのactivateをtrueにします。その後、AccessTokenを返却します。
ログインできない場合はユーザが存在しないものとして、「404:Not Found」を返却します。
destroyアクションは、ログアウトを行います。前回にも書きましたが、ログイン・ログアウトはapi_keysテーブルのactiveの値で管理します。ヘッダーで送られてくるAccessTokenより該当するユーザIDを取得し、そのユーザモデルのinactiveメソッドを呼び出してログアウトを行います。今回はAccessTokenよりユーザを識別しましたが、ここはユーザIDでも良いかもしれません。
app/views/api/v1/user_sessions/create.json.jbuilder
json.user do |json| json.id @user.id json.email @user.email json.name @user.name end json.access_token @access_token
createアクションで返却するJSONの定義です。ユーザ情報とAccessTokenをJSON形式で返却しています。
ApplicationBaseController
上記のコントローラのベースクラスとなるコントローラです。各アクションの前にrequire_valid_tokenメソッドを呼び出し、ヘッダーで送られてくるAccessTokenがログイン済みかを判定します。
app/controllers/api/v1/application_base_controller.rb
module Api module V1 class ApplicationBaseController < ApplicationController before_action :require_valid_token private def require_valid_token access_token = request.headers[:HTTP_ACCESS_TOKEN] if !User.login?(access_token) respond_to do |format| format.json { render nothing: true, status: :unauthorized } end end end end end end
ログイン済みかの判定はUserモデルのlogin?メソッドで行います。ログインしていない場合は「401: unauthorized」を返却します。
SampleController
ログインしている場合と、ログインしていない場合の動きを検証するためのコントローラです。
app/controllers/api/v1/sample_controller.rb
module Api module V1 class SampleController < Api::V1::ApplicationBaseController skip_before_filter :require_valid_token, only: :public def public @message = 'public' end def restrict @message = 'authorized' end end end end
skip_before_filterで、publicアクションはrequire_valid_tokenを呼び出さないようにしています。これにより、publicアクションはログインの有無に関わらず結果を返却することになります。一方restrictはログインしている場合のみ結果を返却します。
public、restrictもJSONで値を返却しています。それを定義しているのが以下のjbuilderで、両方とも同じソースとなります。
api/v1/sample/public.json.jbuilder、app/views/api/v1/sample/restrict.json.jbuilder
json.message @message
ルーティング
最後に、ルーティングの定義も載せておきます。
config/routes.rb
Rails.application.routes.draw do namespace :api do namespace :v1 do resources :users resource :user_sessions, only: [:create, :destroy] get 'sample/public' get 'sample/restrict' end end (以降略)
まとめ
Userの情報自体はUserController、ログイン処理はUserSessionControllerが担当するというのが、今回の特徴かと思います。とはいっても実際のロジックはモデルに記述しているので、次回はモデルについて書いていこうと思います。
今回作成したソースコードは以下のGithubに置いてあります。全ソースを見たい方は参考にしてください。 sorcery_api_sample
参考サイト
今回は以下のサイトを主に参考にさせて頂きました。ありがとうございました。
Simple Password Authentication
Securing an API
Rails + Grape + API Keyの認証