16.

Django で CSRF トークンを Ajax 送信する方法|X-CSRFToken ヘッダー

編集
この記事の要点
  • Django は POST / PUT / PATCH / DELETE に CSRF トークンを要求する。無いと 403 になる
  • Ajax では X-CSRFToken ヘッダーで送る。ヘッダー名は CSRF_HEADER_NAME で決まっている
  • トークンの入手は 2 通り。Cookie csrftoken から読むか、テンプレートに埋め込む
  • CSRF_USE_SESSIONS = TrueCSRF_COOKIE_HTTPONLY = True のときは Cookie から読めない
  • @csrf_exempt で無効化するのは最後の手段。攻撃を通す穴になる

なぜ 403 になるのか

django.middleware.csrf.CsrfViewMiddleware は、安全でないメソッド(POST / PUT / PATCH / DELETE)のリクエストに対して、送られてきたトークンとサーバー側のトークンを突き合わせます。一致しないと次のエラーになります。

Forbidden (403)
CSRF verification failed. Request aborted.
Reason given for failure: CSRF token missing.

通常のフォームでは {% csrf_token %} が hidden の input を出力するので自動的に通ります。Ajax は自分でトークンを付けない限り必ず失敗します。

方法 1: Cookie から読んでヘッダーに付ける(標準的)

Django は既定で csrftoken という Cookie にトークンを入れます。JavaScript でこれを読み、X-CSRFToken ヘッダーに載せます。

function getCookie(name) {
  const m = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)"));
  return m ? decodeURIComponent(m[2]) : null;
}

const csrftoken = getCookie("csrftoken");

fetch("/api/users/", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-CSRFToken": csrftoken,
  },
  body: JSON.stringify({ name: "田中" }),
})
  .then((res) => res.json())
  .then((data) => console.log(data));

jQuery を使っている場合は、全リクエストにまとめて付けられます。

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!/^(GET|HEAD|OPTIONS|TRACE)$/.test(settings.type)) {
      xhr.setRequestHeader("X-CSRFToken", getCookie("csrftoken"));
    }
  },
});

方法 2: テンプレートに埋め込んで読む

CSRF_COOKIE_HTTPONLY = TrueCSRF_USE_SESSIONS = True にしている場合、Cookie を JavaScript から読めません。テンプレート側で値を出力します。

<!-- テンプレート -->
{% csrf_token %}

<script>
  const csrftoken = document.querySelector("[name=csrfmiddlewaretoken]").value;
</script>

フォームが無いページでは、{{ csrf_token }} でトークン文字列そのものを出力できます。

<meta name="csrf-token" content="{{ csrf_token }}">

<script>
  const csrftoken = document.querySelector("meta[name=csrf-token]").content;
</script>

方法 3: フォームデータに含める

JSON ではなくフォーム形式で送る場合は、ヘッダーではなく csrfmiddlewaretoken というフィールド名で本文に入れても通ります。

const form = document.querySelector("#my-form");
fetch("/api/users/", {
  method: "POST",
  body: new FormData(form),   // {% csrf_token %} が入っていれば自動で含まれる
});

FormData を使うときは Content-Type を自分で指定しないでください。境界文字列がブラウザによって付けられるため、手動で書くと壊れます。

ビュー側

import json
from django.http import JsonResponse
from django.views.decorators.http import require_POST

@require_POST
def create_user(request):
    data = json.loads(request.body)      # JSON は request.POST に入らない
    return JsonResponse({"ok": True, "name": data["name"]})

Content-Type: application/json で送ったデータは request.POST ではなく request.body に入ります。ここを取り違えると MultiValueDictKeyError になります。

それでも 403 になるとき

Reason given for failure原因
CSRF token missingヘッダーもフォームフィールドも付いていない
CSRF cookie not setCookie が発行されていない。@ensure_csrf_cookie を付けた GET を一度通す
CSRF token incorrect別のセッションのトークンを使っている。再取得する
Origin checking failedCSRF_TRUSTED_ORIGINSスキーム付きで追加する
Referer checking failedHTTPS で Referer が送られていない。リバースプロキシの設定を見直す
# settings.py
CSRF_TRUSTED_ORIGINS = ["https://example.com"]   # スキームを省略すると起動時にエラー
CSRF_COOKIE_SECURE = True                        # 本番(HTTPS)では有効に
CSRF_COOKIE_SAMESITE = "Lax"

# views.py — SPA などで先に Cookie を発行しておきたい場合
from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def index(request):
    return render(request, "index.html")

@csrf_exempt を使ってよい場面

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt          # CSRF 検証を丸ごと外す
def webhook(request):
    ...

外してよいのは、Cookie による認証を一切使わないエンドポイントだけです。外部サービスからの Webhook などが該当し、その場合は署名検証など別の手段で正当性を確かめます。

「動かないからとりあえず付ける」は、ログイン中のユーザーに意図しない操作をさせる CSRF 攻撃をそのまま通すことになります。ブラウザから叩く画面用の API で外してはいけません。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 環境構築とプロジェクト/アプリの作成
  2. MVC(MVT)のそれぞれの使い方と説明
  3. データベースへの接続と操作
  4. Django Administration
  5. git管理
  6. エラー一覧
  7. バージョンの確認方法
  8. ログ出力方法
  9. SQLのログ出力方法
  10. ログのローテート設定
  11. settings.pyの定数にアクセスする方法
  12. 本番環境へのインストールとアプリのデプロイ(apache編)
  13. 本番環境へのインストールとアプリのデプロイ(nginx編)
  14. djangoアプリの本番の開始URLを変更する
  15. 静的(static)ファイルの置き場所と読み込み(画像、css、js )
  16. CSRFトークンをAjaxで使用する方法
  17. ajaxの使用例(POST編)
  18. ファイルのアップロードとファイルの名前
  19. クイックスタート/チュートリアル
  20. ログイン機能
  21. テンプレート側のログイン判定
  22. ビュー側のログイン判定
  23. 管理者ユーザーの作成/判定と管理画面
  24. モデルのjson化とレスポンス
  25. runserverでポートを指定する方法
  26. cronによるバッチ実行
  27. テンプレートで利用する共通のcontextを定義する方法
  28. プログラムが本番サーバーで反映されない場合の対処法
  29. APIの作成
  30. cron用コマンド・ファイルの作成

最近更新/作成されたページ