Search code examples
angularjsdjangocookiescsrfdjango-csrf

Pass Django CSRF token to Angular with CSRF_COOKIE_HTTPONLY


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.


Solution

  • 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 }
    ]