各 http RSpec テスト ヘッダーに認証トークンを追加する方法
概要
リクエスト仕様を認証しようとして問題が発生しました。各 http リクエストのヘッダーに有効な認証トークンを渡すにはどうすればよいですか?以下の私のアプローチは正しいでしょうか?
ツイート_リクエスト_仕様.rb
require 'rails_helper'
RSpec.describe 'Tweets API', type: :request do
before do
@tweets = create_list(:tweet, 10)
@tweet = @tweets.first
end
describe 'GET /tweets' do
before { get '/tweets', { "Authorization": *some sort of token*} }
it "returns tweets" do
expect(json).to_not be_empty
expect(json).to eq(10)
end
it "is a successful http request" do
expect(response).to have_http_response(200)
end
end
end
これは、認証コントローラーのコードと、http ヘッダーで渡される認証トークンの生成とデコードに役立つモジュールです。
認証コントローラー.rb
class AuthenticationController < ApplicationController
skip_before_action :authenticate_request
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result }
else
render json: { error: command.errors }, status: :authorized
end
end
end
authorize_api_request.rb
class AuthorizeApiRequest
prepend SimpleCommand
def initialize(headers = {})
@headers = headers
end
def call
user
end
private
attr_reader :headers
def user
@user ||= User.find(decoded_auth_token[:user_id]) if decoded_auth_token
@user ||= errors.add(:token, 'Invalid token') && nil
end
#decode the auth token and retrieve the user id
def decoded_auth_token
@decoded_auth_token ||= JSONWebToken.decode(http_auth_header)
end
#retrieve auth token from header
def http_auth_header
if headers['Authorization'].present?
return headers['Authorization'].split(' ').last
else
errors.add(:token, 'Missing token')
end
end
end
解決策
公式pluralsightページからコピーした一部のコード抜粋
認証するエンドポイントは config/routes.rb にあります
post 'authenticate', to: 'authentication#authenticate'
このアクションを実行します。正しく認証された場合、アクションはトークンを返します。
def authenticate
command = AuthenticateUser.call(params[:email], params[:password])
if command.success?
render json: { auth_token: command.result }
else
render json: { error: command.errors }, status: :unauthorized
end
end
rspec には、このメソッドをモックするか、ファクトリを作成するかの 2 つのオプションがあります。
トークンベースの認証の概念は、一度認証されるとユーザーはトークンを持ち、このトークンを提供することでユーザーのみに予約されている機能にアクセスできるようになるというものです。
リクエスト
$ curl -H "Content-Type: application/json" -X POST -d '{"email":"[email protected]","password":"123123123"}' http://localhost:3000/authenticate
応答としてトークンを返します
{"auth_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA"}
ヘッダーにトークンを含めると、リクエストによって認証エラーが発生することはありません。
$ curl -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE0NjA2NTgxODZ9.xsSwcPC22IR71OBv6bU_OGCSyfE89DvEzWfDU0iybMA" http://localhost:3000/items []
したがって、getリクエストを実行する前に、リクエストヘッダーにトークンを含めます。
request.headers['Authorization'] = auth_token
get :your_action
auth_token の正しい値を指定するにはどうすればよいですか?
ApplicationControllerのauthenticate_requestメソッドをモックする必要があります。このメソッドはアクションの前に呼び出されます。
#app/controllers/application_controller.rb
class ApplicationController < ActionController::API
before_action :authenticate_request
attr_reader :current_user
private
def authenticate_request
@current_user = AuthorizeApiRequest.call(request.headers).result
render json: { error: 'Not Authorized' }, status: 401 unless @current_user
end
end
認証エラーが発生しないようにするには、このコード行をモックする必要があると思います。
@current_user = AuthorizeApiRequest.call(request.headers).result
だから私はこのようにスペックを書きます
user = FactoryBot.create(:user)
allow(AuthorizeApiRequest).to receive(:call).and_return(user)
# request.headers['Authorization'] = auth_token # this is not required anymore the authentication is skipped
get :your_action
多視点を引用します
モックについて詳しくは、こちらをご覧ください。
https://translate.google.com/translate?hl=ja&sl=en&tl=ja&u=https://github.com/rspec/rspec-mocks