3
votes

The Issue I'm Having

  • I'm making an Ajax POST request from my Angular2 client to my Django (v1.9) backend (both on localhost, different ports). I'm not yet using the Django REST framework, I'm just dumping the JSON in Django without any add-ons.
  • I have been issued a csrf token by the server, and I'm manually sending it back in the HTTP headers (I can see it there when I make the call).
  • However, I still get the error from django: Forbidden (CSRF cookie not set.)
  • I've read a number of other threads, and tried a few things, but still can't get Django to accept the CSRF token.

Client side code:

private _editUserUri = "http://127.0.0.1:8000/search/edit/user/";

constructor(private _http: Http){}

getCookie(name) {
    let value = "; " + document.cookie;
    let parts = value.split("; " + name + "=");
    if (parts.length == 2) 
      return parts.pop().split(";").shift();
}

validateData(userdata) {
    return true;
}

editUser(userdata) {

    console.log("UserService: createUser function called");
    console.log(JSON.stringify(userdata));

    if(this.validateData(userdata)) {

        let headers = new Headers({
            'Content-Type': 'application/json',
            'X-CSRFToken': this.getCookie('csrftoken')
        });

        let options = new RequestOptions({ headers: headers });

        return this._http
            .post(
                this._editUserUri,
                JSON.stringify(userdata),
                options)
            .map(res => {
                console.log(res.json());
                return res.json();
            })
    }

}

Screenshot of csrf token in header (x-csrftoken). This is exactly how Django expects it (source) Screenshot

What I've tried / considered

  • I thought that perhaps the csrf token is out of date. I've tried to generate a new one, but I haven't yet managed to get Django to issue me a new one. I've tried using the @ensure_csrf_cookie decorator, but still nothing. If I have a csrf token in a cookie which was placed this morning, would I expect it to be replaced by a new, up-to-date version?

  • Another thought was that the header name (x-csrftoken) is in lower case. Django specifies that the header name should be the following: X-CSRFToken. However, from reading online it seems that headers should generally be case sensitive. In any case, Angular2 is automatically converting the header name to lower case.

  • I tried some other solutions I found on Stack Overflow (example) but had no luck.

  • I thought that maybe this was related to CORS, but the first OPTIONS request returns a 200 from my Django server. This pre-flight request wouldn't need to include the csrf token in its headers also, would it? (excuse my ignorance)

  • I'm pretty sure my settings are fine. I have 'django.middleware.csrf.CsrfViewMiddleware' in MIDDLEWARE_CLASSES, CSRF_COOKIE_SECURE = False, CORS_ALLOW_CREDENTIALS = True and CORS_ORIGIN_ALLOW_ALL = True

If anyone could help I'd greatly appreciate it!

Nick

1
You can try passing the token inside csrfmiddlewaretoken POST parameter. Not sure if header name is case sensitive, but you can check it by making a post request through something that doesn't convert it to lowercase (jquery perhaps).serg

1 Answers

2
votes

I think the problem is that your request only has the CSRF token header, but not the cookie (see the double submit mitigation against CSRF, the header you're sending should be compared to the cookie).

I don't quite understand what exactly is happening though. If you elaborate a bit more on where the Angular app is downloaded from and how it gets the CSRF cookie from Django, which is a different origin, I can probably edit this answer to help more. What I don't get is that if the Angular app is downloaded from port A, from where does it have a CSRF cookie for Django on port B.

Edit:

Ah you're using CORS. I think you need withCredentials in the jQuery request so that the csrf cookie is sent with the request:

xhrFields: {
    withCredentials: true
}

Django then needs to send Access-Control-Allow-Credentials: true I think.