Pass Django CSRF token to Angular with CSRF_COOKIE_HTTPONLY
Asked Answered
K

2

8

In Django, when the CSRF_COOKIE_HTTPONLY setting is set to True, the CSRF cookie gains the httponly flag, which is desirable from a security perspective, but breaks the standard angular solution of adding this cookie to the httpProvider like so:

$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';

Through Django 1.9, there was a workaround where you could just pass the cookie directly to the app by putting this in the template:

<script>
    window.csrf_token = "{{ csrf_token }}";
</script>

And putting this in the angular app:

angularApp.config(["$httpProvider", function($httpProvider) {
    $httpProvider.defaults.headers.common["X-CSRFToken"] = window.csrf_token;
}]

Unfortunately, this doesn't work for single page angular apps in Django 1.10+ since the CSRF cookie changes after every request. How do you make post requests from Angular to Django 1.10+ with the CSRF_COOKIE_HTTPONLY setting on?
NB: Disabling CSRF protection is not an acceptable answer.

Kallick answered 28/11, 2016 at 18:55 Comment(0)
R
1

Django has a documented solution for this. Any Javascript can get the CSRF token from the DOM even if CSRF_COOKIE_HTTPONLY is enabled as long as the CSRF token is in the DOM.

Step 1: I add a tag to let Django middleware put csrf token to the DOM

# Django put CSRF token to DOM
{% csrf_token %}

Step 2: Implement an HttpInterceptor to get csrf from DOM

import { Injectable } from '@angular/core';
import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class DjangoCSRFInterceptor implements HttpInterceptor {
  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = (
      document.querySelector('[name=csrfmiddlewaretoken]') as HTMLInputElement
    ).value;
    return httpRequest.clone({ headers: httpRequest.headers.set('X-CSRFToken, token) });
  }
}

Step 3: Put HttpInterceptor to your Angular module providers

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: DjangoCSRFInterceptor, multi: true }
]
Raby answered 3/7, 2022 at 23:17 Comment(0)
S
1

I think this question was answered well in this discussion.

https://groups.google.com/forum/#!topic/django-developers/nXjfLd8ba5k

https://code.djangoproject.com/ticket/27534

CSRF_COOKIE_HTTPONLY does not provide any additional security for single page apps. Some people reccomended this solution

var csrftoken = getCookie('csrftoken');
if (csrftoken === null) {
    csrftoken = $('input[name="csrfmiddlewaretoken"]').val();
    if (csrftoken === null) {
        console.log('No csrf token');
    }
}

however either or if you are exposing csrftoken for your app restfully nothing stops the malcious user from taking it and using it as well. If you are running a single page app you might as well set CSRF_COOKIE_HTTPONLY=False, As per comment bellow:

Gavin Wahl 5/4/15

How so? You cannot just ajax-fetch stuff from different domains.

I'm talking about a single domain. Injected javascript on a page that doesn't contain the CSRF token can fetch a different page on the same domain to get it.

If you already injected javascript onto the victims page (XSS) there is no need to fetch the CSRF token, you already got greater control.

Well, the HttpOnly flag is intended to reduce the damage an attacker can do once the already can inject javascript. But I agree -- hiding the CSRF token from javascript doesn't increase security in any way, but the documentation implies it does. I want to make it clear in the documentation that this setting has no meaningful effect.

If you still think you have a valid attack vector there, please send it to [email protected] and add a bit more explanation.

There is no attack vector. This is just about misleading documentation.

Strut answered 14/5, 2018 at 3:31 Comment(1)
To summarize: "You can't. Don't use the CSRF_COOKIE_HTTPONLY setting"Kallick
R
1

Django has a documented solution for this. Any Javascript can get the CSRF token from the DOM even if CSRF_COOKIE_HTTPONLY is enabled as long as the CSRF token is in the DOM.

Step 1: I add a tag to let Django middleware put csrf token to the DOM

# Django put CSRF token to DOM
{% csrf_token %}

Step 2: Implement an HttpInterceptor to get csrf from DOM

import { Injectable } from '@angular/core';
import { HttpEvent, HttpRequest, HttpHandler, HttpInterceptor } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class DjangoCSRFInterceptor implements HttpInterceptor {
  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const token = (
      document.querySelector('[name=csrfmiddlewaretoken]') as HTMLInputElement
    ).value;
    return httpRequest.clone({ headers: httpRequest.headers.set('X-CSRFToken, token) });
  }
}

Step 3: Put HttpInterceptor to your Angular module providers

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: DjangoCSRFInterceptor, multi: true }
]
Raby answered 3/7, 2022 at 23:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.