web_bonsaiの日記

web開発の学習日記です。誰に見せるためでもないただの日記です。

POSTしたときに422エラーでActionController::InvalidAuthenticityTokenになって失敗したので解決したい | Mac + Docker + Rails + Next.js その0048

参考にさせていただいたページ

はじめに

解決できませんでしたが色々試行錯誤した個人的なログとして残しています。

環境について

この記事はローカル環境での話です。

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.)

おわりに

難しい。