[Ruby on Rails]sorceryによる認証 – (5)APIでの認証 #2 コントローラ

2015.06.09

はじめに

前回に引き続き、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の認証