Devise gem は config.http_authenticatable_on_xhr
が true
のとき、未認証XHRに対してリダイレクトするのではなく、401 Unauthorizedで応答します。
config/initializers/devise.rb
Devise.setup do |config|
config.http_authenticatable_on_xhr = true
end
specは下記のようになります。
※context「XHRのとき」とcontext「非XHRのとき」の振る舞いの差はDevise由来なので、どちらかだけでも十分かと思います。
`describe 'GET /api/v1/posts' do
subject(:req) do
post api_v1_posts_path, headers: headers
response
end
let(:headers) { {} }
context 'ログインしていないとき' do
context 'XHRのとき' do
let(:headers) { { 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest' } }
it '401 Unauthorizedで応答する' do
is_expected.to have_http_status(:unauthorized)
end
end
context '非XHRのとき' do
it '管理者サインイン画面にリダイレクトする' do
is_expected.to redirect_to('/admins/sign_in')
end
end
end
context '管理者としてログインしているとき' do
let(:admin) { create(:admin) }
before { sign_in admin }
it '200 OKで応答する' do
is_expected.to have_http_status(:ok)
end
end
end
仕組み
WardenがXHRリクエストであるかを判定し、DeviseのFailureAppが具体的な応答を決めています。
(仕組みについては、GitMCPを利用して色々と調べると勉強になりました。MCPは楽しいですね)
経緯
既存プロジェクトの改修範囲をカバーするためのrequest specについて、初めはこのように書いていました:
describe 'GET /api/v1/posts' do
subject(:req) do
post api_v1_posts_path
response
end
context 'ログインしていないとき' do
it '管理者サインイン画面にリダイレクトする' do
is_expected.to redirect_to('/admins/sign_in')
end
end
context '管理者としてログインしているとき' do
let(:admin) { create(:admin) }
before { sign_in admin }
it '200で応答する' do
is_expected.to have_http_status(:ok)
end
end
end
これでもNGにはならないのですけど、「APIなのにリダイレクト?」と疑問に思ってE2Eで確認したところ401で応答していたので、本稿の調査となりました。
環境
rails (7.1.5.1)
devise (4.9.4)
rspec-rails (7.1.1)