この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
前回に引き続き、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の認証