utamaro’s blog

誰かの役に立つ情報を発信するブログ

DjangoでCSRFを含んだリクエストをtagを使って実行する方法

CSRFトークンを含んだAPIの実行方法で、ドキュメントに載っているような方法ではなく、tagを使った方法を紹介します。

tagを使った方法というのは、{% cookie 'csrftoken' %}でhtml内にtokenを入れて、その値を使う方法です。

修正が必要な箇所は以下の3箇所です。

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があった)しました。

あえなく断念しました。