Search code examples
djangoangulardjango-rest-frameworkcsrf-protectiondjango-csrf

What is the recommended way to have CSRF protection in a Django Rest Framework + Angular application?


I have been struggling with a configuration between Django and Angular, and I am missing something. What is the recommended way of doing that?

Angular has some XSRF protection, but it has changed since AngularJS and I found a lot of outdated information or manual. Django uses some defaults, but they don't behave well with Angular.

What is the batteries-included way of solving CSRF between DRF and Angular?


Solution

  • The Angular recommended way is explained in the HTTP Guide but, for some of us, it has some tricky steps. Fortunately, in recent versions of Angular, you need minimal changes. And all the default/recommended CSRF middleware behaves well once things have been set up. There is some information DRF-specific and also a reference to the Django official documentation

    The minimal changes that, right now, are working for me (Django 2.1, Angular 6, and up-to-date version of dependencies) are the following

    Django side

    First of all, you need to set up Django to send the cookie as Angular expects:

    # settings.py
    CSRF_USE_SESSIONS = False
    CSRF_COOKIE_HTTPONLY = False  # this is the default, and should be kept this way
    CSRF_COOKIE_NAME = 'XSRF-TOKEN'
    CSRF_HEADER_NAME = 'HTTP_X_XSRF_TOKEN'
    

    Caution! Remember that cookie names are case sensitive. I lost a lot of time there :(

    Note that the CSRF cookie should be accessible by the Javascript, so httpOnly flag should not be set to True.

    Here I am assuming that you already have a functional authentication system and are using both django.contrib.sessions.middleware.SessionMiddleware and django.middleware.csrf.CsrfViewMiddleware (typically you will find those modules listed in the MIDDLEWARE variable in the settings.py).

    Angular side

    Then, activate the CSRF at the angular side:

    # app.module.ts
    import {HttpClientModule, HttpClientXsrfModule} from '@angular/common/http';
    
    ...
    
    @NgModule({
      imports: [
        // ...
        HttpClientModule,
        HttpClientXsrfModule,
      ]
    // ...
    

    If you don't want to change Django default name for CSRF Header and Cookie, you can instead change them at the Angular side, by changing HttpClientXsrfModule, line for:

    HttpClientXsrfModule.withOptions({
      cookieName: 'csrftoken',
      headerName: 'X-CSRFTOKEN',
    }),
    

    I have not fully tested this configuration. Remember that, at the Django side, you have to change a little bit the CSRF_HEADER_NAME (at least: hyphen-to-underscores and HTTP_ prefix). The previous example should match de fault value of HTTP_X_CSRFTOKEN. The default CSRF_COOKIE_NAME Django setting is csrftoken (lowercase).

    If you have problems with custom namings, check the actual headers inserted by Angular (with developer tools of your browser), and you can double check how are you receiving them in Django by debugging the content of request.META