DjangoでCSRFを含んだリクエストをtagを使って実行する方法
CSRFトークンを含んだAPIの実行方法で、ドキュメントに載っているような方法ではなく、tagを使った方法を紹介します。
tagを使った方法というのは、{% cookie 'csrftoken' %}
でhtml内にtokenを入れて、その値を使う方法です。
修正が必要な箇所は以下の3箇所です。
- Django
- html(template)
- javascript
Djangoの対応
まずは、テンプレートから使用できるようにtagを作成します。
作成する場所は、プロジェクトディレクトリがある場所にtemplate_tags/tags.py
というファイルを作成しました。
@register.simple_tag(takes_context=True) def cookie(context, cookie_name): # could feed in additional argument to use as default value request = context['request'] result = request.COOKIES.get(cookie_name, '') # I use blank as default value return result
次にsettings.pyを編集します。
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], 'libraries': { # ←これを追加 'tags': 'template_tags.tags', } }, }, ]
tagのファイルを読み込むようにしています。
これでDjango側の対応は終わりです。
html(template)の対応
Djangoで使用するtemplate html内に以下のように書きます。
<meta name="csrf" content="{% cookie 'csrftoken' %}">
最終的にhtmlのmetaタグにcsrfを追加して、jsで読み込んで使用します。
<meta name="csrf" content="W3ni7NdZeaPWYgfoRShxFVNLHCimZWxpZsL8QMJ4VtlBguv9lo7NoYGO7Gl0eD0D">
javascriptの対応
javascriptの対応も簡単です。
function call(callback) { $.ajax({ url:'/api/hoge', type:'POST', beforeSend: function(request) { let csrftoken = $("meta[name='csrf']").attr('content') request.setRequestHeader("X-CSRFToken", csrftoken); }, data:{ 'limit': 100, } }) .done(function(data) { console.log(data); callback(data); }) .fail(function(data) { console.log(data); }) }
この部分を追加すると対応が可能です。
beforeSend: function(request) { let csrftoken = $("meta[name='csrf']").attr('content') request.setRequestHeader("X-CSRFToken", csrftoken); },
また、状況に合わせて以下のコードを入れると良いとおもいます。
csrfが必要なリクエストと、不要なリクエストを分けて、必要ならcsrftokenありでリクエストするものです。
function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } function ajaxInit() { $.ajaxSetup({ beforeSend: function(xhr, settings) { let csrftoken = getCookie('csrftoken'); if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); }
tagを使う方法はstackoverflowで見つけました。
python - Django read cookie in template tag - Stack Overflow
失敗パターン
csrf_tokenで対応しようとして失敗
Djangoではformでリクエストを実行する際に{% csrf_token %}
というものを使えます。
これを使えばtoken値を上記みたいなコードを使わずに簡単に実装できるのではと考えました。
metaタグにcsrf_tokenを入れれると思ったのですが…
<meta name="csrf" content="{% csrf_token %}">
{% csrf_token %}
が展開されると<input>
タグが作成されるのでダメでした。
画面上に/>
という文字列が表示されていたり、domがめちゃくちゃになったり(body直下にheadがあった)しました。
あえなく断念しました。