1

I've been studying about both AngularJS and Django recently. AngularJS promises to handle incoming cookie named 'XSRF-TOKEN' and to add a request header named 'X-XSRF-TOKEN' automatically. So I set django variables in settings.py:

CSRF_COOKIE_NAME = 'XSRF-TOKEN'
CSRF_HEADER_NAME = 'X-CSRF-TOKEN'

Obviously, it is not working as is implied here. So I had to install angular-cookies.js module to retrieve 'csrftoken' cookie and put its value in request header 'X-CSRF-TOKEN'. However, it's still not working. So what ever it is this time, it must be on server side.

HTML template:

<!DOCTYPE html>
<html>
    <head>
        <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
        <title>youtube-dl</title>
        <link href="/video/css/video.css" rel="stylesheet" type="text/css">
        <script src="/js/angular-1.6.4.min.js" type="text/javascript"></script>
        <script src="/js/angular-cookies.js" type="text/javascript"></script>
    </head>
    <body>
        <div id="container" class="center" ng-app="vc" ng-controller="vc-ctl">
            <img id="dl-icon" src="/video/logo.png"></img>
            <form name="form">
                {% csrftoken %}
                <input class="url {{urlState}}" type="url" name="url" placeholder="https://" required autofocus ng-model="url">
                <button class="btn btn1" ng-if="!form.url.$valid">
                    {{ buttonText }}
                </button>
                <button class="btn btn2 {{btn2State}}" ng-if="form.url.$valid" ng-click="click();">
                    {{ buttonText }}
                </button>
            </form>
        </div>

        <script src="/video/js/app.js" type="text/javascript"></script>
    </body>
</html>

views.py:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import json

from django.shortcuts import render
from django.http import HttpResponse

from .models import VideoTask

# Create your views here.
def index(request):
    if request.method == 'GET':
        return render(request, 'video/index.html')

    stat = {
        "full_path: ", request.get_full_path(),
        "host: ", request.get_host(),
        "port: ", request.get_port(),
        "is_ajax: ", request.is_ajax(),
        "is_secure: ", request.is_secure(),
        "data: ", request.read(),
    }
    return HttpResponse(json.dumps(stat))

settings.py:

CSRF_COOKIE_NAME = 'csrftoken'
CSRF_HEADER_NAME = 'X-CSRF-TOKEN'

app.js:

var app = angular.module("vc", [
    'ngCookies'
]);
app.run(function($rootScope, $http, $cookies) {
    $rootScope.buttonText = "Submit URL";
    $http.defaults.headers.post['X-CSRF-TOKEN'] = $cookies.csrftoken;
});
app.controller("vc-ctl", function($scope, $http, $cookies) {
    $scope.cb_success = function(res) {
        $scope.progList = res.data.progList;
    };
    $scope.cb_failure = function(res) {
        console.log(res.status);
    };
    $scope.postUrl = "/wsgi/video/";
    $scope.click = function() {
        //$scope.btn2State = "hidden";
        var data = encodeURIComponent($scope.url);
        var cookie_csrf = $cookies.get('csrftoken');
        console.log("Cookie: \"" + cookie_csrf + "\"");
        console.log("POST {data: \"" + data + "\"}");
        var req = {
            method: 'POST',
            url: $scope.postUrl,
            headers: {
                'X-CSRF-TOKEN': cookie_csrf,
            },
            data: {
                url: data
            }
        };
        $http(req).then($scope.cb_success, $scope.cb_failure);
    };
    $scope.update = function() {
        $http.post($scope.postUrl, {"action": "echo"}).then($scope.cb_success, $scope.cb_failure);
    };
});

Obviously I have {% csrftoken %} in my HTML form. When I check my data parcels in Firefox, I have both headers, 'Cookie' and 'X-CSRF-TOKEN'.

Error page screenshot: enter image description here

Cœur
  • 37,241
  • 25
  • 195
  • 267
KaiserKatze
  • 1,521
  • 2
  • 20
  • 30

1 Answers1

0

Solution:

Edit settings.py, set CSRF_HEADER_NAME = 'HTTP_X_CSRF_TOKEN'

How I find the solution:

django CSRF protection is implemented in /usr/local/lib/python2.7/dist-packages/django/middleware/csrf.py where I located the function process_view throwed the exception CSRF token missing or incorrect:

request_csrf_token = ""
if request.method == "POST":
    try:
        request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
    except IOError:
        pass
# <TEST>
print( '[STAGE - 1] request_csrf_token=', request_csrf_token, _unsalt_cipher_token(request_csrf_token) )
# </TEST>
if request_csrf_token == "":
    request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
    # <TEST>
    print( '[STAGE - 2] request_csrf_token=', request_csrf_token, _unsalt_cipher_token(request_csrf_token) )
    print('request.META:')
    for k, v in request.META.items():
       print('', k, v)
    # </TEST>
    request_csrf_token = _sanitize_token(request_csrf_token)
    # <TEST>
    print( '[STAGE - 3] request_csrf_token=', request_csrf_token, _unsalt_cipher_token(request_csrf_token) )
    print( 'csrf_token=', csrf_token, _unsalt_cipher_token(csrf_token) )
    # </TEST>
    if not _compare_salted_tokens(request_csrf_token, csrf_token):
        return self._reject(request, REASON_BAD_TOKEN)

As is shown, I inserted a few lines to help determine what's going on in this function.

To my supprise, when I check out /var/log/apache2/error.log to retrieve django output, I found request_csrf_token is empty string in both STAGE-1 and STAGE-2, aka, request.POST.get('csrfmiddlewaretoken', '') and request.META.get(settings.CSRF_HEADER_NAME, '') had no value for request_csrf_token.

So I checked out request.META dictionary, BOOM, an entry named HTTP_X_CSRF_TOKEN has exactly what I want, a valid CSRF token.

Obviously django has this BUG that you can't customize the name of CSRF token HTTP request header name (CSRF_HEADER_NAME) as is promised. django also failed to keep its document compliance with its code, because the so-called default value of CSRF_HEADER_NAME is HTTP_X_CSRFTOKEN in its document.

KaiserKatze
  • 1,521
  • 2
  • 20
  • 30