65

I have django running on an apache server using mod_wsgi, as well as an angularjs app served directly by apache, not by django. I would like to make POST calls to the django server (running rest_framework) but I am having problems with the csrf token.

Is there someway to set the token from the server without putting {% csrf token %} as part of the template (since these pages aren't going through django)?

  1. I would like to be able to get a csrf token through a GET request as a cookie.
  2. I would like to be able to then make POST requests to the django server with the csrf token cookie value.
Hieu Nguyen
  • 8,563
  • 2
  • 36
  • 43
Preom
  • 1,680
  • 3
  • 15
  • 20

5 Answers5

112

Django and AngularJS both have CSRF support already, your part is quite simple.

First, you need to enable CSRF in Django, I believe you have already done so, if not, follow Django doc https://docs.djangoproject.com/en/1.5/ref/contrib/csrf/#ajax.

Now, Django will set a cookie named csrftoken on the first GET request and expects a custom HTTP header X-CSRFToken on later POST/PUT/DELETE requests.

For Angular, it expects the cookie named XSRF-TOKEN and will do POST/PUT/DELETE requests with X-XSRF-TOKEN header, so you need to do a little bit tweak to make the two go with each other:

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

Add above two lines somewhere in your js code, module.config() block is a good place for this.

That's it.

NOTE: This is for angular 1.1.5, older versions might need different approach.

Update:

Since the angular app isn't served by django, in order to let the cookie to be set, angular app needs to do a GET request to django first.

Ye Liu
  • 8,946
  • 1
  • 38
  • 34
  • He still needs to set the cookie somehow. – dan-klasson Aug 09 '13 at 22:47
  • @dan-klasson You're right, his angular app isn't served by Django. – Ye Liu Aug 09 '13 at 22:53
  • I gave you a +1 for the explanation on csrf and because it set me on the right path. Unfortunately, this didn't work for me, and I couldn't find a reference to the `xsrfCookieNAme` and and `xsrfHeaderName` in the official docs. Thank you, though. – Preom Aug 10 '13 at 06:27
  • @Preom FYI, `xsrfCookieName` and and `xsrfHeaderName` are documented after angular 1.2. – Ye Liu May 12 '14 at 13:42
  • Awesome answer: both right to the point and informative. Worked like a charm ! – ereOn May 21 '14 at 07:07
  • The bit under UPDATE got me, angular app was served as static html so I wasn't getting the token in the first place – James Jul 06 '15 at 14:47
  • 3
    I am getting the Set-Cookie header in login/ api which is POST. When should I call the GET request and how? – Asim K T Feb 14 '16 at 06:57
60
var foo = angular.module('foo', ['bar']);

foo.config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.xsrfCookieName = 'csrftoken';
    $httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
}]);

And all modules services and controllers, where $http used, will send requests with csrf token.

Nikolay Baluk
  • 2,245
  • 1
  • 19
  • 22
11

After searching around, what worked for me was from this post with the following code:

angular.module( '[your module name]',
    ... [some dependencies] ...
    'ngCookies',
    ... [other dependencies] ...
)
.run( function run( $http, $cookies ){

    // For CSRF token compatibility with Django
    $http.defaults.headers.post['X-CSRFToken'] = $cookies.get('csrftoken');
})

This is of course after getting the cookie through a GET request from the django server.

I also looked into some of the other answers here, including Ye Liun's but couldn't find anything in the official docs specifying changes to the defaults options for xsrf on $httpProvider, other than this pull request which didn't work for me at the time of me writing this post.

Shatran
  • 35
  • 4
Preom
  • 1,680
  • 3
  • 15
  • 20
  • I'm seeing a problem with doing things this way. When I emulate a first-time site visit in chrome with `ctrl`+`shift`+`n`, `$cookies['csrftoken'];` is `undefined` since I haven't sent any requests to my django server when that line is executed. I do an `OPTIONS` request first and then set the `$http.defaults.headers.post['X-CSRFToken']` globally since the `$cookies.csrftoken` is then valid. – Ross Rogers May 31 '14 at 00:08
  • I solved the undefined issue by calling `$cookiesProvider.$get()` to generate a new local `$cookies` from `document.cookies` and then updating the csrf value on the `$http` defaults. I did this by shamefully creating a global reference to `$cookiesProvider`: `var cookiesProvider_ref = null; app.config( function($cookiesProvider) {cookiesProvider_ref = $cookiesProvider });`. I then use `cookiesProvider_ref` in my login `success()` callback to set the `$http` csrf token header field again. – Ross Rogers Feb 19 '15 at 18:10
  • I had to eventually just use the `getCookie` function recommended from Django's site as I preferred doing that to including a pointless extra trip to the server when the cookie was already sent down. https://docs.djangoproject.com/en/dev/ref/csrf/#ajax – jaywhy13 Jun 30 '15 at 18:54
1

I created a Django App for my AngularJS app, in the same Django project as my (REST) API Django App, that only serves the index.html file (which is just a sym.link). In this way the CSRF Cookie is set without an additional GET request.

Please see my answer here about AngularJS Single Page Web Application on Sub-domain A talking to a Django JSON (REST) API on Sub-domain B using CORS and CSRF protection

Community
  • 1
  • 1
Visionscaper
  • 3,979
  • 1
  • 23
  • 26
0

If you have your cookies set to disallow javascript access, you need to do the following. In your template, before creating the django app, add this:

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

In your angular app, add this:

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

At least through Django 1.9, the CSRF token does not change with each request. It only changes when a user logs in. If you are doing a single page angular app, you need to make sure you reset the token on login/logout and this should work fine.

NOTE: This does not currently work in Django 1.10 or later due to the CSRF token changing on each request. See Pass Django CSRF token to Angular with CSRF_COOKIE_HTTPONLY

Community
  • 1
  • 1
Zags
  • 37,389
  • 14
  • 105
  • 140