CSRF(Cross-Site Request Forgery)は、ユーザーが意図しないリクエストを別サイトから送信させる攻撃手法です。Django はこの攻撃をフレームワークレベルで防御しており、開発者は {% csrf_token %} をテンプレートに書くだけで対策が完了します。
CSRF 攻撃とは
CSRF 攻撃のシナリオを簡単に整理します。
ユーザー自身はボタンを押した覚えがないのに、ログイン済みのセッションを悪用されてしまうわけです。
ユーザーが正規サイトにログイン中
攻撃者サイトが裏でリクエストを送信
セッションクッキーが自動送信される
正規サイトが正当なリクエストと誤認
Django の CSRF 防御の仕組み
Django は「トークンベース」の防御を採用しています。仕組みは次のとおりです。
フォームを表示するとき、Django はランダムなトークン(CSRF トークン)を生成し、フォームの hidden フィールドとクッキーの両方に埋め込みます。POST リクエストを受け取ったとき、Django はフォームから送信されたトークンとクッキーのトークンを比較し、一致しなければ 403 エラーを返します。
攻撃者はユーザーのクッキーを読み取れないため、正しいトークンをフォームに埋め込むことができません。これにより、外部サイトからの不正なリクエストがブロックされます。
csrf_token タグの使い方
テンプレートの <form> タグ内に {% csrf_token %} を記述するだけで、hidden フィールドが自動生成されます。
<form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">送信</button> </form>
レンダリングされた HTML を見ると、次のような hidden フィールドが挿入されていることがわかります。
<input type="hidden" name="csrfmiddlewaretoken" value="Ek8Qx7...(ランダムなトークン)">
この 1 行を忘れるだけで、POST リクエストはすべて 403 Forbidden になります。Django を使い始めた直後に最もよく遭遇するエラーの 1 つが、この {% csrf_token %} の書き忘れです。
CsrfViewMiddleware
CSRF 防御を担当しているのは django.middleware.csrf.CsrfViewMiddleware です。settings.py の MIDDLEWARE にデフォルトで含まれています。
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
このミドルウェアは以下のルールで動作します。
安全なメソッドとみなし、CSRF 検証をスキップする。データを変更しないリクエストにはトークンは不要。
データを変更する可能性があるため、CSRF トークンを検証する。トークンが不一致または欠落していれば 403 エラーを返す。
AJAX リクエストでの CSRF 対策
JavaScript から POST リクエストを送る場合は、クッキーからトークンを取得してリクエストヘッダーに含めます。
function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } fetch('/api/articles/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': getCookie('csrftoken') }, body: JSON.stringify({ title: 'Hello' }) });
Django は X-CSRFToken ヘッダーも CSRF トークンとして受け入れます。フォームの hidden フィールドを使えない AJAX 通信では、この方法が標準的なアプローチになります。
csrf_exempt で特定のビューを除外する
外部 API からの Webhook 受信など、CSRF トークンを送れないケースでは @csrf_exempt デコレータで特定のビューだけ CSRF 検証を無効化できます。
from django.views.decorators.csrf import csrf_exempt @csrf_exempt def webhook(request): # 外部サービスからの POST を受け付ける ...
ただし、@csrf_exempt はそのビューの CSRF 防御を完全に無効化するため、代わりに API キーの検証やシグネチャの照合など、別の認証手段を必ず用意してください。
フォーム送信時の標準的な CSRF 対策。テンプレートに 1 行追加するだけ
AJAX 通信での CSRF 対策。クッキーからトークンを取得してヘッダーに設定する
なぜ GET にはトークンが不要なのか
HTTP の仕様では、GET リクエストは「安全なメソッド」に分類されています。GET はデータの取得のみを行い、サーバーの状態を変更しないことが前提です。そのため Django は GET リクエストに対して CSRF 検証を行いません。
逆に言えば、GET リクエストでデータベースの変更や削除を行う設計は避けるべきです。「データを変更する操作は POST で」という HTTP の基本原則を守ることが、CSRF 対策の前提条件になっています。
CSRF 対策は Django が自動で処理してくれますが、その仕組みを理解しておくことで、403 エラーの原因特定や AJAX 通信での適切な対応が素早くできるようになります。