下のようなインフラ構成でPOSTする際にActionController::InvalidAuthenticityToken
のエラーが出てしまったので、出てしまった理由と解決策をメモしていきたいと思います。ELBまではhttps通信で、そこから先はhttp通信で行っていることを前提とします。

ActionController::InvalidAuthenticityToken
とは
CSRFの対策に関係するエラーです。
まずはCSRF(クロスサイトリクエストフォージェリ)とはというところから書いていきます。
CSRFとは
「重要な処理」に対して利用者の意図したリクエストであることを確認する必要がありますが、これが抜けていると罠サイトを閲覧しただけで、利用者のブラウザから勝手に「重要な処理」を実行させられる場合があります。「重要な処理」の例をあげます。
- 利用者のアカウントによる物品の購入
- 利用者のアカウントによるSNSや問い合わせフォームなどへの書き込み
- 利用者のパスワードやメールアドレスの変更など
CSRFが生まれる原因
CSRFが生まれる背景としては以下のWebの性質があります。
- form要素のaction属性にはどのドメインのURLでも指定できる。
- クッキーに保管されたセッションは対象サイトに自動的に送信される。
CSRFの例としてはRailsガイドが分かりやすかったです。
CSRF対策
対策として、正規利用者の意図したリクエストであることを確認することが重要です。具体的には以下の2つが挙げられます。
秘密情報の埋め込み
これはRailsでもやられている手法です。application_html.erbを見ると<%= csrf_meta_tags %>が埋め込まれていて、ブラウザの表示を見るとトークンが生成されていることが分かります。

また、_<アプリ名>_sessionという形でクッキーを送信します。Railsはこれらを比較して、正しいリクエストであることの確認をします。
リクエスト元の確認
CSRFの手法では罠サイトからのアクセスになるので、オリジンヘッダーは罠サイトになります。対象の操作でこれを確認することでCSRFの対策になります。
今回ハマったエラー
ところで今回ハマったエラーはというと、ActionController::InvalidAuthenticityToken
HTTP Origin header (https://example.com) didn't match request.base_url (http://example.com)
というエラーです。エラー文を読むと、オリジンヘッダーと実際に届いたリクエストのオリジンが正しくないと言われています。なぜそんなことが起こっているのでしょうか。オリジンとはスキーマ、ホスト、ポートからなるものです。インフラ構成ではELBまではHTTPSでそこから先はHTTPで通信しています。つまり、オリジンヘッダーはhttps://example.comに対して、実際に届いたリクエストはhttp://example.comということになります。このため、エラーが起こっていたことが分かります。
具体的なコードでいうとこの部分です。
def unverified_request_warning_message # :nodoc:
if valid_request_origin?
"Can't verify CSRF token authenticity."
else
"HTTP Origin header (#{request.origin}) didn't match request.base_url (#{request.base_url})"
end
end
vaild_request_origin?がfalseなら該当のエラーがでそうです
def valid_request_origin? # :doc:
if forgery_protection_origin_check
# We accept blank origin headers because some user agents don't send it.
raise InvalidAuthenticityToken, NULL_ORIGIN_MESSAGE if request.origin == "null"
request.origin.nil? || request.origin == request.base_url
else
true
end
end
request.origin == request.base_urlがfalseならこのメソッドからfalseが返りそうです。request.base_urlはRackのメソッドです。
def base_url
"#{scheme}://#{host_with_port}"
end
schemaメソッドを見てみます。
def scheme
if get_header(HTTPS) == 'on'
'https'
elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
'https'
elsif forwarded_scheme
forwarded_scheme
else
get_header(RACK_URL_SCHEME)
end
end
ようやく現れました。
HTTP_X_FORWARDED_SSLヘッダーがonならhttpsになるようです。NginxへはhttpでくるのでそこでユーザからはSSL通信ですよということを明示的に設定する必要があるようです。
location @unicorn {
# 略
proxy_set_header X-Forwarded-SSL on; #追記
}
これは少し余談ですが、ELBを使っているとヘッダーを勝手に書き換えてしまうようです。なので、Nginx側で設定が必要になるというわけですね。
これでエラーが消えました👏
参考
https://qiita.com/ruru8/items/345bec2f8aff64cd0962