👓

Devise Token Authで/sing_outを呼び出すとundefined method `destroy'が出た話

2022/10/24に公開

問題

$ bundle exec rspec
Failures:

  1) Api::V1::Sessions logout as an authenticated user success logout and APIs require authentication result in a 401 error
     Failure/Error: delete '/api/v1/auth/sign_out', headers: auth_params

     NoMethodError:
       undefined method `destroy' for {"warden.user.user.key"=>#<User email: "test2@example.com", provider: "email", uid: "test2@example.com", id: 100, allow_password_change: [FILTERED], name: "TestUser", nickname: nil, image: nil, created_at: "2022-10-17 13:21:07.448428000 +0000", updated_at: "2022-10-17 13:21:07.461190000 +0000"}:RackSessionFixController::FakeRackSession

             session.destroy
                    ^^^^^^^^
     # /usr/local/bundle/gems/warden-1.2.9/lib/warden/manager.rb:36:in `block in call'
     # /usr/local/bundle/gems/warden-1.2.9/lib/warden/manager.rb:34:in `catch'
     # /usr/local/bundle/gems/warden-1.2.9/lib/warden/manager.rb:34:in `call'
     # /usr/local/bundle/gems/rack-2.2.4/lib/rack/etag.rb:27:in `call'
     # /usr/local/bundle/gems/rack-2.2.4/lib/rack/conditional_get.rb:40:in `call'
     # /usr/local/bundle/gems/rack-2.2.4/lib/rack/head.rb:12:in `call'
     # /usr/local/bundle/gems/railties-7.0.4/lib/rails/rack/logger.rb:40:in `call_app'
     # /usr/local/bundle/gems/railties-7.0.4/lib/rails/rack/logger.rb:25:in `block in call'
     # /usr/local/bundle/gems/railties-7.0.4/lib/rails/rack/logger.rb:25:in `call'
     # /usr/local/bundle/gems/rack-2.2.4/lib/rack/runtime.rb:22:in `call'
     # /usr/local/bundle/gems/rack-2.2.4/lib/rack/sendfile.rb:110:in `call'
     # /usr/local/bundle/gems/railties-7.0.4/lib/rails/engine.rb:530:in `call'
     # /usr/local/bundle/gems/rack-test-2.0.2/lib/rack/test.rb:358:in `process_request'
     # /usr/local/bundle/gems/rack-test-2.0.2/lib/rack/test.rb:155:in `request'
     # ./spec/requests/api/v1/sessions_api_spec.rb:36:in `block (5 levels) in <top (required)>'
     # ./spec/requests/api/v1/sessions_api_spec.rb:35:in `block (4 levels) in <top (required)>'

Finished in 0.09395 seconds (files took 0.64071 seconds to load)
2 examples, 1 failure

Failed examples:

rspec ./spec/requests/api/v1/sessions_api_spec.rb:34 # Api::V1::Sessions logout as an authenticated user success logout and APIs require authentication result in a 401 error

環境

  • Ruby 3.1.2
  • Rails 7.0.4
  • devise 4.8.1
  • devise_token_auth 1.2.1
$ rails routes
                         Prefix Verb   URI Pattern                           Controller#Action
        new_api_v1_user_session GET    /api/v1/auth/sign_in(.:format)        api/v1/sessions#new
            api_v1_user_session POST   /api/v1/auth/sign_in(.:format)        api/v1/sessions#create
    destroy_api_v1_user_session DELETE /api/v1/auth/sign_out(.:format)       api/v1/sessions#destroy
cancel_api_v1_user_registration GET    /api/v1/auth/cancel(.:format)         api/v1/registrations#cancel
   new_api_v1_user_registration GET    /api/v1/auth/sign_up(.:format)        api/v1/registrations#new
  edit_api_v1_user_registration GET    /api/v1/auth/edit(.:format)           api/v1/registrations#edit
       api_v1_user_registration PATCH  /api/v1/auth(.:format)                api/v1/registrations#update
                                PUT    /api/v1/auth(.:format)                api/v1/registrations#update
                                DELETE /api/v1/auth(.:format)                api/v1/registrations#destroy
                                POST   /api/v1/auth(.:format)                api/v1/registrations#create
     api_v1_auth_validate_token GET    /api/v1/auth/validate_token(.:format) devise_token_auth/token_validations#validate_token

コード

sessions_controller.rb
class Api::V1::SessionsController < DeviseTokenAuth::SessionsController
end
application_controller.rb
class ApplicationController < ActionController::API
  include DeviseTokenAuth::Concerns::SetUserByToken
 
  # Rails 7でDeviseを使用するめのパッチ
  # https://github.com/heartcombo/devise/issues/5443
  include RackSessionFixController
end
rack_session_fix_controller.rb
module RackSessionFixController
  extend ActiveSupport::Concern

  class FakeRackSession < Hash
    def enabled?
      false
    end
  end

  included do
    before_action :set_fake_rack_session_for_devise
    
    private

    def set_fake_rack_session_for_devise
      request.env['rack.session'] ||= FakeRackSession.new
    end
  end
end
sessions_api_spec.rb
require 'rails_helper'
require 'json'

RSpec.describe 'Api::V1::Sessions', type: :request do 
  describe 'logout' do
    context 'as an authenticated user' do
      let(:user) { FactoryBot.attributes_for(:user) }

      it 'success logout and APIs require authentication result in a 401 error' do
        auth_params = sign_in(user)
        aggregate_failures do
          delete '/api/v1/auth/sign_out', headers: auth_params
          expect(response).to have_http_status(:success)

          get '/api/v1/mypage', headers: auth_params
          expect(response).to have_http_status(401)
        end
      end
    end
  end
end

対応方法

sessions_controller.rb
class Api::V1::SessionsController < DeviseTokenAuth::SessionsController
  skip_after_action :reset_session, only: [:destroy]

  def destroy
    super
    session["warden.user.user.key"] = nil
  end
end

または

sessions_controller.rb
class Api::V1::SessionsController < DeviseTokenAuth::SessionsController
  private
 
  def reset_session
    session["warden.user.user.key"] = nil
  end
end

reset_sessionが走るとエラーとなる模様。そのためreset_sessionをスキップ or 再定義することでエラーとならないように対応しました。
reset_sessionRailsのドキュメントを見る限り、セッション情報を削除したいだけみたいなので、自前でセッション削除処理を追加してみたら、エラーが解消されました。

Discussion