参考にさせていただいたページ
- 【Rails】 API開発で『Can't verify CSRF token authenticity』といわれたときの対応 - Qiita
- 【Rails API】CSRF 対策をあきらめないでちゃんとやる | みどりみちのブログ
- Rails API + SPAのCSRF対策例
- RailsのCSRF保護を詳しく調べてみた(翻訳)|TechRacho by BPS株式会社
- RailsでAPI用のアプリを作成(POST処理編) - 親バカエンジニアのナレッジ帳
はじめに
解決できませんでしたが色々試行錯誤した個人的なログとして残しています。
環境について
この記事はローカル環境での話です。
docker-composeで、railsアプリケーションには https://localhost
でアクセスできるようにしていて、next.jsアプリケーションには https://localhost:10443
だったり、https://localhost:3001
だったりでアクセスできるようにしています。
解決したいログ(ActionController::InvalidAuthenticityToken)
https://localhost:10443
からhttps://localhost
に、POSTリクエストしたときにrailsのログに以下のように表示されました。
| Started POST "/api/tasks" for 172.25.0.1 at 2022-09-11 00:48:57 +0000 | Cannot render console from 172.25.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1 | Processing by Api::TasksController#post as */* | Parameters: {"name"=>"aaaaaaaaaa", "description"=>"", "task"=>{"name"=>"aaaaaaaaaa", "description"=>""}} | HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost) | Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 444) | | | | ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)): | | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token' | activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'
ログを読んでみる
以下の部分はデバッグ用のrails consoleに関するメッセージのため、今回は関係ありません。
| Cannot render console from 172.25.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1
送信したパラメーターのオブジェクト構造が間違っていますね。
| Parameters: {"name"=>"aaaaaaaaaa", "description"=>"", "task"=>{"name"=>"aaaaaaaaaa", "description"=>""}}
送信したNext.jsのアプリケーションのオリジン(https://localhost:10443)とRailsアプリケーションのオリジン(https://localhost)が違います。
| ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)):
ステータスコード422であることがわかります。
| Completed 422 Unprocessable Entity in 2ms (ActiveRecord: 0.0ms | Allocations: 444)
request_forgery_protection.rb
の中で判定をしていそうです。
| actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token'
パラメータが違う
まずパラメータが違いますね。
{ name: 'aaaaaaaaaa', description: '', }
を送っていますが、
{ task: { name: 'aaaaaaaaaa', description: '', }, }
を送らないといけませんでした。今回の場合。
パラメータを修正して再送信してログを確認する
パラメータを修正して再度POSTしたら以下のようになりました。
パラメータに関するログが変化しましたが、ステータスコード422であることには変化がありません。
| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:15:24 +0000 | Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1 | Processing by Api::TasksController#post as */* | Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}} | HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost) | Completed 422 Unprocessable Entity in 3ms (ActiveRecord: 0.0ms | Allocations: 446) | | | | ActionController::InvalidAuthenticityToken (HTTP Origin header (https://localhost:10443) didn't match request.base_url (https://localhost)): | | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token' | activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'
オリジンのチェックをしないようにする
rails/config/environments/development.rb
に以下の設定を追加します。
# クライアント側とドメインが異なるため、クロスオリジンの検証を無効にする config.action_controller.forgery_protection_origin_check = false
docker-compose down と up をし直す
一度 docker-compose down して up し直します。
再送信してログを確認する
再度POSTしたら以下のようになりました。
HTTP Origin headerに関するログが2箇所変化しましたが、ステータスコード422であることには変化がありません。
| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:31:36 +0000 | Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1 | ActiveRecord::SchemaMigration Pluck (4.1ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC | Processing by Api::TasksController#post as */* | Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}} | Can't verify CSRF token authenticity. | Completed 422 Unprocessable Entity in 1ms (ActiveRecord: 0.0ms | Allocations: 583) | | | | ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.): | | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:251:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:284:in `handle_unverified_request' | actionpack (7.0.3.1) lib/action_controller/metal/request_forgery_protection.rb:273:in `verify_authenticity_token' | activesupport (7.0.3.1) lib/active_support/callbacks.rb:400:in `block in make_lambda'
一旦AuthenticityTokenの確認を無効にする
あくまで一旦です。
セキュリティ面を犠牲にして、とにかくPOSTできるようにしたい。ということになってしまいます。
AuthenticityTokenの確認を無効にしたいcontrollerに以下の記述を追加します。
今回の私の場合は、rails/app/controllers/api/tasks_controller.rb
を編集します。
class TasksController < ApplicationController skip_before_action :verify_authenticity_token 〜略〜 end
再送信してログを確認する
再度POSTしたら以下のようになりました。
ステータスコード200になって、レコードが追加されました。
| Started POST "/api/tasks" for 172.24.0.1 at 2022-09-16 06:48:12 +0000 | Cannot render console from 172.24.0.5! Allowed networks: 127.0.0.0/127.255.255.255, ::1 | Processing by Api::TasksController#post as */* | Parameters: {"task"=>{"name"=>"aaaaaaaaaa", "description"=>""}} | TRANSACTION (0.4ms) BEGIN | ↳ app/controllers/api/tasks_controller.rb:18:in `post' | Task Exists? (0.7ms) SELECT 1 AS one FROM "tasks" WHERE "tasks"."name" = $1 LIMIT $2 [["name", "aaaaaaaaaa"], ["LIMIT", 1]] | ↳ app/controllers/api/tasks_controller.rb:18:in `post' | Task Create (12.5ms) INSERT INTO "tasks" ("name", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "aaaaaaaaaa"], ["description", ""], ["created_at", "2022-09-16 06:48:12.631905"], ["updated_at", "2022-09-16 06:48:12.631905"]] | ↳ app/controllers/api/tasks_controller.rb:18:in `post' | TRANSACTION (2.2ms) COMMIT | ↳ app/controllers/api/tasks_controller.rb:18:in `post' | Task Load (0.3ms) SELECT "tasks".* FROM "tasks" ORDER BY "tasks"."created_at" DESC | ↳ app/controllers/application_controller.rb:3:in `map' | Completed 200 OK in 39ms (Views: 0.2ms | ActiveRecord: 16.1ms | Allocations: 5153)
AuthenticityTokenの確認を有効にする
先ほど追加した以下の行を削除します。
skip_before_action :verify_authenticity_token
AuthenticityTokenをちゃんと確認してPOSTできるようにする
レスポンスヘッダーにAuthenticityTokenを入れるメソッドを定義する
レスポンスヘッダーにauthenticity_tokenを入れるためのset_csrf_token_header
メソッドをrails/app/controllers/application_controller.rb
に定義します。
class ApplicationController < ActionController::Base def set_csrf_token_header response.set_header('X-CSRF-Token', form_authenticity_token) end end
各アクションの後にset_csrf_token_headerを実行する
今回の私の場合は、rails/app/controllers/api/tasks_controller.rb
を編集します。
各アクションの後にset_csrf_token_headerメソッドを実行します。
class TasksController < ApplicationController after_action :set_csrf_token_header 〜略〜 end
X-CSRF-Tokenヘッダをexposeする
rack-corsの設定を変更します。
rails/config/initializers/cors.rbのを編集します。
expose: ['X-CSRF-Token'],
を追記して以下のようにします。
Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins "*" resource "*", headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head], expose: ['X-CSRF-Token'] end end
docker-compose down と up をし直す
一度 docker-compose down して up し直します。
ブラウザでレスポンスヘッダーを確認する
再度POSTして、ブラウザでレスポンスヘッダーを確認したら、以下のようにX-CSRF-Tokenが追加されていることが確認できました。
X-CSRF-Tokenを受け取るためのエンドポイントの作成
POSTする前に受け取りたいのでそのためのエンドポイントを作成します。
X-CSRF-Tokenを受け取る
以下のようにすると受け取れました。
const token = response.headers.get('X-CSRF-Token');
X-CSRF-Tokenをリクエストヘッダーに入れてPOSTしてみる
POSTしてみたものの以下の状態は解消されず、一旦諦めました...。
ActionController::InvalidAuthenticityToken (Can't verify CSRF token authenticity.)
おわりに
難しい。