CSRF対策：SPAとMPAでの対策方法について

# CSRF対策とは？

CSRF（Cross-Site Request Forgery）対策は、悪意あるサイトやスクリプトがユーザーになりすまして不正なリクエストをサーバーに送る攻撃を防ぐための仕組みです。簡単に言うと、「**ユーザー本人しかできない操作**」と「**不正なリクエスト**」を区別する仕組みを導入することです。

### CSRF攻撃が発生する仕組み
1. ユーザーがログイン中の状態で、悪意のあるサイトやメールに誘導される。
2. 悪意のあるサイトに埋め込まれたスクリプトが、ログイン状態のセッションを利用してサーバーに不正なリクエストを送信する。
3. サーバーはリクエストが正当かどうかを確認せず、そのリクエストに従ってしまう。
   - 例えば、ユーザー本人が意図していないお金の送金や情報の変更など。

### CSRF対策の基本的な仕組み
CSRF対策として、**CSRFトークン**を使うのが一般的です。CSRFトークンとは、「このリクエストはユーザー本人から来ていることを確認するための特別なパスワードのようなもの」です。

#### CSRFトークンの流れ
1. **サーバーがページを表示する際、CSRFトークンを生成**し、そのページ内のフォームやリクエストと一緒にトークンを含めます。
2. **ユーザーが操作を行うとき**、そのリクエストにはCSRFトークンが含まれます。
3. **サーバーはリクエストを受け取ったとき**に、送信されたCSRFトークンが正しいか（サーバーが発行したものと一致しているか）を確認します。
4. トークンが一致しなければ、「このリクエストは正当なユーザーからのものではない」と判断して、リクエストを拒否します。

### CSRFトークンを使う理由
悪意のあるサイトからのリクエストには、正しいCSRFトークンが含まれていないため、サーバーは不正なリクエストと判断できるわけです。これによって、ユーザーの意図しない操作がサーバーで実行されるのを防ぎます。

### 簡単な例
1. **トークンなし**：家の玄関に鍵がなく、誰でも自由に開け閉めできる。
2. **トークンあり**：家の玄関に鍵があり、家の人だけが持っている鍵でしか開けられない。

つまり、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トークンをフォームに追加します。

```erb
<%= form_with(url: '/submit', method: :post) do |form| %>
  <%= form.label :name %>
  <%= form.text_field :name %>
  <%= form.submit %>
<% end %>
```

Railsは、上記のフォーム内に次のような隠しフィールドを自動で追加します。

```html
<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>` 部分に次のような記述があります。

```html
<meta name="csrf-token" content="<%= csrf_meta_tags %>">
```

これにより、JavaScriptでCSRFトークンを取得し、AJAXリクエストのヘッダーに追加できます。

#### JavaScriptでのCSRFトークンの設定例
```javascript
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対策
1. **CSRFトークンの発行と保存**
   - Rails APIサーバーは、ログイン成功時や初回リクエストで**CSRFトークン**を発行します。
   - 発行されたCSRFトークンはクライアント側（Reactアプリ）で保存します。一般的には、`localStorage`や`sessionStorage`、あるいはHTTP-onlyなクッキーで保存します。
   - HTTP-onlyなクッキーにトークンを保存することで、JavaScriptからのアクセスを防ぎ、セキュリティを強化できます。

2. **リクエスト時のCSRFトークン送信**
   - 保存したCSRFトークンを、すべてのAPIリクエストのヘッダーに付加します。例えば、リクエストヘッダーに `X-CSRF-Token` というキーでトークンを含めます。
   - Rails API側で、このヘッダーのトークンを検証し、不正なリクエストをブロックします。

   以下のように、Reactのコードでリクエストにトークンを追加できます。

   ```javascript
   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' })
   })
   ```

3. **Rails側の設定**
   - RailsのAPIモードでは、デフォルトで `protect_from_forgery` が無効になっていますが、CSRFを必要とする場合は、以下のように `protect_from_forgery with: :exception` を追加し、CSRFトークンがヘッダーに含まれているかを検証します。

   ```ruby
   class ApplicationController < ActionController::API
     include ActionController::RequestForgeryProtection
     protect_from_forgery with: :null_session

     before_action :verify_authenticity_token, unless: -> { request.format.json? }
   end
   ```

4. **クッキーを使ったトークン管理**
   - RailsでCSRFトークンをクッキーとして発行し、クライアントがこれを利用できるようにする方法もあります。Railsは、`cookies["X-CSRF-Token"]` のようにクッキーにトークンを保存し、Reactアプリがそれをリクエストヘッダーにセットすることで、APIリクエストの際に自動的にCSRFトークンが含まれるようになります。

5. **セキュリティヘッダーの設定**
   - APIとSPA構成では、`SameSite=Lax`または`SameSite=Strict`クッキー属性も設定すると、セッションやトークンが第三者によって不正に使用されるのを防げます。

### まとめ
- **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から直接アクセスできないようにし、セキュリティを強化します。この「ダブルサブミッションクッキー」方式は、クライアントとサーバー間でトークンの一致を確認するための重要な対策です。

1. **Rails側でCSRFトークンの生成とクッキーへのセット**
   - Rails API側で、CSRFトークンを生成し、HTTP-onlyかつ`SameSite=Strict`属性を持たせたクッキーとしてユーザーに送信します。

   ```ruby
   class SessionsController < ApplicationController
     def create
       # ログイン成功時にCSRFトークンをHTTP-onlyクッキーにセット
       cookies['csrf_token'] = {
         value: form_authenticity_token,
         httponly: true,
         same_site: :strict
       }
       # 他のログイン処理
     end
   end
   ```

2. **クライアントからのリクエストでCSRFトークンを送信**
   - クライアントがリクエストを送信する際、クッキーに格納されたCSRFトークンがサーバー側に自動的に送信されるため、JavaScriptから直接トークンを設定する必要はありません。

3. **サーバーでのトークン検証**
   - リクエストを受け取ると、サーバー側で`X-CSRF-Token` ヘッダーのトークンとHTTP-onlyクッキーのトークンを比較して検証します。トークンが一致しない場合は、不正なリクエストとして処理を中止します。

---

#### 対処法2: Content Security Policy（CSP）の設定

CSPヘッダーを活用して、外部の悪意あるスクリプトからのトークンアクセスを制限することで、CSRFトークンがJavaScriptにアクセスされるリスクを減らします。特に、ReactとRails API構成でのCSP設定が有効です。

1. **CSP設定のGemインストールと初期設定**
   - Rails API側で、`secure_headers` や `rack-csp` などのGemを利用して、CSPヘッダーを設定します。まずは、Gemをインストールします。

     ```ruby
     # Gemfile
     gem 'secure_headers'
     ```

2. **Railsの初期設定でCSPを設定する**
   - 設定ファイル（例: `config/initializers/secure_headers.rb`）を作成し、Reactアプリで必要なリソースのみ許可するCSPポリシーを記述します。

     ```ruby
     # 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: トークンの頻繁な再発行と短い有効期限

トークンを頻繁に再発行し、有効期限を短く設定することで、トークンが漏洩しても短時間しか使えないようにする対策です。

1. **Railsでトークンの再発行と短い有効期限の設定**
   - Railsでは、リクエストごとに新しいトークンを発行することでセキュリティを強化できます。`force_ssl`を使用してトークンがHTTPS経由でのみ送信されるように設定するのも有効です。

2. **期限切れのトークンを検出してエラーを返す**
   - クッキーに設定したトークンが期限切れである場合、Rails側でエラーを返し、React側で再認証やリフレッシュを行う処理を実装します。

---

#### 対処法4: SameSite属性によるクロスサイトリクエスト制限

`SameSite` クッキー属性を活用して、クロスサイトからのリクエスト時にCSRFトークンが送信されないよう制限します。

1. **SameSite属性をStrictまたはLaxに設定**
   - Railsのクッキー設定で`SameSite=Strict`を設定することで、異なるサイトからのリクエストが発生した場合、クッキーが送信されないようにします。

   ```ruby
   cookies['csrf_token'] = {
     value: form_authenticity_token,
     same_site: :strict
   }
   ```

2. **クロスサイトのセキュリティ強化**
   - `Strict`が厳しすぎる場合は、`Lax`に設定し、同一サイト内でのリクエストにはクッキーが送信されるようにすることで、柔軟性を確保しつつセキュリティを強化します。

---

#### まとめ
以上の対策を組み合わせることで、CSRFトークンがJavaScriptからアクセスされるリスクを最小限に抑えることができます。Rails APIとReact SPA構成でのセキュリティをさらに向上させるために、各対策を環境に合わせて適切に設定しましょう。