CSRF対策とは?
CSRF(Cross-Site Request Forgery)対策は、悪意あるサイトやスクリプトがユーザーになりすまして不正なリクエストをサーバーに送る攻撃を防ぐための仕組みです。簡単に言うと、「ユーザー本人しかできない操作」と「不正なリクエスト」を区別する仕組みを導入することです。
CSRF攻撃が発生する仕組み
- ユーザーがログイン中の状態で、悪意のあるサイトやメールに誘導される。
- 悪意のあるサイトに埋め込まれたスクリプトが、ログイン状態のセッションを利用してサーバーに不正なリクエストを送信する。
- サーバーはリクエストが正当かどうかを確認せず、そのリクエストに従ってしまう。
- 例えば、ユーザー本人が意図していないお金の送金や情報の変更など。
CSRF対策の基本的な仕組み
CSRF対策として、CSRFトークンを使うのが一般的です。CSRFトークンとは、「このリクエストはユーザー本人から来ていることを確認するための特別なパスワードのようなもの」です。
CSRFトークンの流れ
- サーバーがページを表示する際、CSRFトークンを生成し、そのページ内のフォームやリクエストと一緒にトークンを含めます。
- ユーザーが操作を行うとき、そのリクエストにはCSRFトークンが含まれます。
- サーバーはリクエストを受け取ったときに、送信されたCSRFトークンが正しいか(サーバーが発行したものと一致しているか)を確認します。
- トークンが一致しなければ、「このリクエストは正当なユーザーからのものではない」と判断して、リクエストを拒否します。
CSRFトークンを使う理由
悪意のあるサイトからのリクエストには、正しいCSRFトークンが含まれていないため、サーバーは不正なリクエストと判断できるわけです。これによって、ユーザーの意図しない操作がサーバーで実行されるのを防ぎます。
簡単な例
- トークンなし:家の玄関に鍵がなく、誰でも自由に開け閉めできる。
- トークンあり:家の玄関に鍵があり、家の人だけが持っている鍵でしか開けられない。
つまり、CSRF対策のCSRFトークンは、「家に入っていいのは家の人だけ」という認証の役割を果たし、悪意のある第三者が操作することを防ぐものです。
RailsでのMPA(Multi-Page Application)のCSRF対策方法は?
RailsでのMPA(Multi-Page Application)の場合、CSRF対策は基本的にフレームワークが自動的に処理してくれますが、具体的な対策の仕組みについて解説します。
1. CSRFトークンの自動生成と埋め込み
Railsでは、CSRF対策がデフォルトで有効になっています。コントローラに protect_from_forgery
が設定されており、Railsが自動的にリクエストの中にCSRFトークンを埋め込む仕組みが組み込まれています。
CSRFトークンの仕組み
- Railsは、サーバー側でCSRFトークンを生成し、それをビューに埋め込みます。
- HTMLフォームを生成する際、自動的にCSRFトークンがフォームに含まれるようにします。
例えば、次のようにフォームを作成すると、Railsが自動的にCSRFトークンをフォームに追加します。
<%= form_with(url: '/submit', method: :post) do |form| %>
<%= form.label :name %>
<%= form.text_field :name %>
<%= form.submit %>
<% end %>
Railsは、上記のフォーム内に次のような隠しフィールドを自動で追加します。
<input type="hidden" name="authenticity_token" value="CSRFトークンの値">
この authenticity_token
がCSRFトークンで、サーバー側で生成したトークンと一致するかを確認するために使われます。
2. リクエスト時のトークン確認
- ユーザーがフォームを送信すると、CSRFトークンも一緒に送信されます。
- サーバーは、リクエストに含まれるCSRFトークンがセッション内のトークンと一致するかどうかを確認します。
- 一致していればリクエストを受け入れ、不一致の場合は「リクエストが正当でない」と判断し、エラーを返してリクエストを拒否します。
3. 非同期リクエスト(AJAX)でのCSRFトークン利用
MPAであっても、非同期リクエストを使う場合には、CSRFトークンをJavaScriptから利用できるようにする必要があります。
CSRFトークンをJavaScriptから取得する方法
Railsは、JavaScriptでもCSRFトークンを取得しやすいように、meta
タグを使ってCSRFトークンを埋め込んでくれます。例えば、application.html.erb
の <head>
部分に次のような記述があります。
<meta name="csrf-token" content="<%= csrf_meta_tags %>">
これにより、JavaScriptでCSRFトークンを取得し、AJAXリクエストのヘッダーに追加できます。
JavaScriptでのCSRFトークンの設定例
document.addEventListener('DOMContentLoaded', function() {
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
fetch('/your_endpoint', {
method: 'POST',
headers: {
'X-CSRF-Token': token,
'Content-Type': 'application/json'
},
body: JSON.stringify({ your_data: 'value' })
})
});
4. Railsでの標準設定を維持すること
Railsはデフォルトで protect_from_forgery
を有効にしているため、基本的に設定をそのままにしておけば、CSRF対策が機能します。特にカスタマイズが必要でない限り、Railsのデフォルトのセキュリティ設定を維持することが推奨されます。
まとめ
RailsのMPAでのCSRF対策は、主に以下の仕組みによって行われます。
- Railsが自動生成するCSRFトークンをフォームに埋め込み、リクエスト時にサーバーが確認。
- 非同期リクエストで必要な場合、JavaScript経由でトークンを取得しリクエストヘッダーに設定。
これにより、ユーザー以外からの不正リクエストを防ぐことができます。
以下の見出しに従い、Rails APIとReact SPA構成でのCSRF対策に関する記事をまとめます。
rails apiモードとreactの場合のSPA構成でのCSRF対策
RailsをAPIモードで使用し、フロントエンドにReactを使うSPA構成の場合、Rails側のCSRF対策を通常のMPA(マルチページアプリケーション)とは少し異なる方法で設定する必要があります。SPA構成では、主にAPIリクエストを通じてデータをやり取りするため、APIリクエストにCSRFトークンを正しく含めることでCSRF攻撃を防ぐことが重要です。
Rails APIモードとReact SPAでのCSRF対策
-
CSRFトークンの発行と保存
- Rails APIサーバーは、ログイン成功時や初回リクエストでCSRFトークンを発行します。
- 発行されたCSRFトークンはクライアント側(Reactアプリ)で保存します。一般的には、
localStorage
やsessionStorage
、あるいはHTTP-onlyなクッキーで保存します。 - HTTP-onlyなクッキーにトークンを保存することで、JavaScriptからのアクセスを防ぎ、セキュリティを強化できます。
-
リクエスト時のCSRFトークン送信
- 保存したCSRFトークンを、すべてのAPIリクエストのヘッダーに付加します。例えば、リクエストヘッダーに
X-CSRF-Token
というキーでトークンを含めます。 - Rails API側で、このヘッダーのトークンを検証し、不正なリクエストをブロックします。
以下のように、Reactのコードでリクエストにトークンを追加できます。
const token = localStorage.getItem('csrfToken'); fetch('/api/your_endpoint', { method: 'POST', headers: { 'X-CSRF-Token': token, 'Content-Type': 'application/json' }, body: JSON.stringify({ data: 'your_data' }) })
- 保存したCSRFトークンを、すべてのAPIリクエストのヘッダーに付加します。例えば、リクエストヘッダーに
-
Rails側の設定
- RailsのAPIモードでは、デフォルトで
protect_from_forgery
が無効になっていますが、CSRFを必要とする場合は、以下のようにprotect_from_forgery with: :exception
を追加し、CSRFトークンがヘッダーに含まれているかを検証します。
class ApplicationController < ActionController::API include ActionController::RequestForgeryProtection protect_from_forgery with: :null_session before_action :verify_authenticity_token, unless: -> { request.format.json? } end
- RailsのAPIモードでは、デフォルトで
-
クッキーを使ったトークン管理
- RailsでCSRFトークンをクッキーとして発行し、クライアントがこれを利用できるようにする方法もあります。Railsは、
cookies["X-CSRF-Token"]
のようにクッキーにトークンを保存し、Reactアプリがそれをリクエストヘッダーにセットすることで、APIリクエストの際に自動的にCSRFトークンが含まれるようになります。
- RailsでCSRFトークンをクッキーとして発行し、クライアントがこれを利用できるようにする方法もあります。Railsは、
-
セキュリティヘッダーの設定
- APIとSPA構成では、
SameSite=Lax
またはSameSite=Strict
クッキー属性も設定すると、セッションやトークンが第三者によって不正に使用されるのを防げます。
- APIとSPA構成では、
まとめ
- Rails API は、セッションやクッキーでCSRFトークンをクライアントに発行し、リクエストヘッダーにトークンを含めることで不正なアクセスをブロックします。
- Reactアプリ側 は、発行されたトークンをヘッダーに付与してAPIリクエストを行い、CSRF攻撃を防止します。
- Rails APIのCSRFトークンとHTTP-onlyクッキー設定を活用すると、よりセキュアなCSRF対策が実現できます。
このようにして、Rails APIとReact SPA構成でのCSRF対策を実施できます。
CSRFトークンがJavaScriptからアクセスできてしまう場合の対処法
ReactとRails APIの構成では、CSRFトークンがJavaScriptからアクセスできる状態が、悪意あるスクリプトによる不正利用を招くリスクを含んでいます。ここでは、そのリスクを最小限にするための対処法を紹介し、各設定方法を詳しく解説します。
対処法1: HTTP-onlyなクッキーを使ってトークンを保存する
CSRFトークンをHTTP-only属性を持つクッキーに保存することで、JavaScriptから直接アクセスできないようにし、セキュリティを強化します。この「ダブルサブミッションクッキー」方式は、クライアントとサーバー間でトークンの一致を確認するための重要な対策です。
-
Rails側でCSRFトークンの生成とクッキーへのセット
- Rails API側で、CSRFトークンを生成し、HTTP-onlyかつ
SameSite=Strict
属性を持たせたクッキーとしてユーザーに送信します。
class SessionsController < ApplicationController def create # ログイン成功時にCSRFトークンをHTTP-onlyクッキーにセット cookies['csrf_token'] = { value: form_authenticity_token, httponly: true, same_site: :strict } # 他のログイン処理 end end
- Rails API側で、CSRFトークンを生成し、HTTP-onlyかつ
-
クライアントからのリクエストでCSRFトークンを送信
- クライアントがリクエストを送信する際、クッキーに格納されたCSRFトークンがサーバー側に自動的に送信されるため、JavaScriptから直接トークンを設定する必要はありません。
-
サーバーでのトークン検証
- リクエストを受け取ると、サーバー側で
X-CSRF-Token
ヘッダーのトークンとHTTP-onlyクッキーのトークンを比較して検証します。トークンが一致しない場合は、不正なリクエストとして処理を中止します。
- リクエストを受け取ると、サーバー側で
対処法2: Content Security Policy(CSP)の設定
CSPヘッダーを活用して、外部の悪意あるスクリプトからのトークンアクセスを制限することで、CSRFトークンがJavaScriptにアクセスされるリスクを減らします。特に、ReactとRails API構成でのCSP設定が有効です。
-
CSP設定のGemインストールと初期設定
-
Rails API側で、
secure_headers
やrack-csp
などのGemを利用して、CSPヘッダーを設定します。まずは、Gemをインストールします。# Gemfile gem 'secure_headers'
-
-
Railsの初期設定でCSPを設定する
-
設定ファイル(例:
config/initializers/secure_headers.rb
)を作成し、Reactアプリで必要なリソースのみ許可するCSPポリシーを記述します。# config/initializers/secure_headers.rb SecureHeaders::Configuration.default do |config| config.csp = { default_src: ["'self'"], script_src: ["'self'"], style_src: ["'self'", "'unsafe-inline'"], img_src: ["'self'", "data:"], connect_src: ["'self'", "https://your-react-app-domain.com"], font_src: ["'self'", "https://fonts.googleapis.com"], frame_src: ["'none'"] } end
-
これにより、信頼されたドメイン以外からのスクリプトの読み込みを防ぎ、悪意のあるスクリプトがトークンにアクセスすることを防止します。
-
対処法3: トークンの頻繁な再発行と短い有効期限
トークンを頻繁に再発行し、有効期限を短く設定することで、トークンが漏洩しても短時間しか使えないようにする対策です。
-
Railsでトークンの再発行と短い有効期限の設定
- Railsでは、リクエストごとに新しいトークンを発行することでセキュリティを強化できます。
force_ssl
を使用してトークンがHTTPS経由でのみ送信されるように設定するのも有効です。
- Railsでは、リクエストごとに新しいトークンを発行することでセキュリティを強化できます。
-
期限切れのトークンを検出してエラーを返す
- クッキーに設定したトークンが期限切れである場合、Rails側でエラーを返し、React側で再認証やリフレッシュを行う処理を実装します。
対処法4: SameSite属性によるクロスサイトリクエスト制限
SameSite
クッキー属性を活用して、クロスサイトからのリクエスト時にCSRFトークンが送信されないよう制限します。
-
SameSite属性をStrictまたはLaxに設定
- Railsのクッキー設定で
SameSite=Strict
を設定することで、異なるサイトからのリクエストが発生した場合、クッキーが送信されないようにします。
cookies['csrf_token'] = { value: form_authenticity_token, same_site: :strict }
- Railsのクッキー設定で
-
クロスサイトのセキュリティ強化
-
Strict
が厳しすぎる場合は、Lax
に設定し、同一サイト内でのリクエストにはクッキーが送信されるようにすることで、柔軟性を確保しつつセキュリティを強化します。
-
まとめ
以上の対策を組み合わせることで、CSRFトークンがJavaScriptからアクセスされるリスクを最小限に抑えることができます。Rails APIとReact SPA構成でのセキュリティをさらに向上させるために、各対策を環境に合わせて適切に設定しましょう。