Ruby on Rails 5で認証付きのAPIを作りたいというケースがあります。
このエントリでは、Ruby on Rails 5でJWTを使った認証付きAPIの作り方と、RSpecを使ったテストの書き方を説明します。
ちなみにJWTはジョットと発音するそうです。
ApplicationControllerに認証のメソッドを追加し、before_action
に設定します。
認証に失敗した場合、ステータスコード403(Forbidden)を返すようにしています。
ただし、実際にはもっと細かくJWTのエラー(claim)をハンドリングしなければならないことに注意してください。
ここでは説明のシンプルさを優先しています。
ここではJWT を扱うにあたって ruby-jwt というRuby Gemsを使っています。
class ApplicationController < ActionController::Base
before_action :authenticate
private
def authenticate
_, token = request.headers['Authorization'].split(' ')
@payload, = JWT.decode token, 'api secret', true, algorithm: 'HS256'
rescue JWT::DecodeError
head :forbidden
end
end
authenticate
が認証用のメソッドです。
このように、actionとして呼ばれることを想定していないメソッドはプライベートにしておきましょう。
Only public methods are callable as actions. It is a best practice to lower the visibility of methods (with private or protected) which are not intended to be actions, like auxiliary methods or filters.
これでApplicationControllerを継承したControllerはbefore_action
で認証が課されるになります。
テストのポイントは、「認証のテストをどう書くか」ということと「Controller のテストでどうやって認証を回避するか」ということでしょう。
1つずつ見ていきましょう。
認証のテストはControllerのテスト(controller spec)としてではなく、Requestのテスト(request spec)として書きます。
Request Specにもactionが必要になるので、先に適当なControllerとaction, routesを追加しておきましょう。
class CatsController < ApplicationController
def index
render json: []
end
end
Rails.application.routes.draw do
resources :cats, only: [:index]
end
Request Specは次のようになります。
type: :request
とすることで Request Specになります。
require 'rails_helper'
RSpec.describe 'Authentication', type: :request do
let(:payload) { { 'data' => 'test' } }
let(:api_secret) { 'api secret' }
let(:algorithm) { 'HS256' }
let(:token) { JWT.encode payload, api_secret, algorithm }
let(:headers) { { 'Authorization' => "Bearer #{token}" } }
describe 'ApplicationController#authenticate' do
describe 'response status' do
subject do
get '/cats', headers: headers
response
end
context 'invalid token' do
let(:token) { 'invalid token' }
it { is_expected.to have_http_status(:forbidden) }
end
context 'valid token' do
it { is_expected.to have_http_status(:ok) }
end
end
end
end
Request Spec とController Specとでは、getが取り得る引数に違いがあるので注意しましょう。
Controller Specで認証を回避するには、ApplicationControllerで定義したauthenticate
メソッドをstubしてあげれば良いです。
しかしControllerのテストを書くたびにauthenticate
をstubするというのはかなり煩雑です。
そこで、authenticate
メソッドをstubするためのHelperモジュールを作りincludeします。
まずHelperを次のように定義します。
module AuthenticationHelper
extend ActiveSupport::Concern
included do
before do
allow_any_instance_of(ApplicationController).to receive(:authenticate)
end
end
end
ActiveSupport::Concern
がわからない方は、良い解説記事がたくさんあるので調べてみてください。
このモジュールのポイントは、includeされたときにauthenticate
メソッドをstubしていることです。
これにより、モジュールをincludeするだけで認証がスキップされるため、使い勝手が良くなります。
次に、このモジュールをrails_helper.rb
でincludeします。
...
RSpec.configure do |config|
...
config.include AuthenticationHelper, type: :controller, authentication: :skip
end
これでtype: :controller, authentication: :skip
オプションのある全てのController Specで認証がスキップされるようになりました。
Controller Specで認証を回避するには、次のように書きます。
RSpec.describe CatsController, type: :controller, authentication: :skip do
...
end
以上です。
このエントリでは、Ruby on Rails 5でJWTを使った認証付きAPIの作り方と、RSpecを使ったテストの書き方を説明しました。
コメントを送る
コメントはブログオーナーのみ閲覧できます