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
Since I'm currently working on a similar setup and was battling to get CORS to work properly in combination with CSRF protection, I wanted to share my own learnings here.
Setup - The SPA and the API are both on different sub-domains of the same domain:
- AngularJS (1.2.14) Single Page Web Application on sub-domain app.mydomain.com
- Django App (1.6.2) implements a JSON REST API on sub-domain api.mydomain.com
The AngularJS app is served through a Django App in the same project as the Django API APP such that it sets a CSRF Cookie. See, for instance, also How to run multiple websites from one Django project
Django API App - In order to get CORS and CSRF protection working I needed to do the following at the API backend.
In settings.py for this app (an extension of the Django project settings.py):
- Add the corsheaders app and middleware and the CSRF middleware:
INSTALLED_APPS = (
...
'corsheaders',
...
)
MIDDLEWARE_CLASSES = (
...
'django.middleware.csrf.CsrfViewMiddleware',
...
'corsheaders.middleware.CorsMiddleware',
)
Also see Django CORS headers on GitHub
- Add the domain for the SPA Webapp to the CORS_ORIGIN_WHITELIST
CORS_ORIGIN_WHITELIST = [
...
'app.mydomain.com',
...
]
- Set CORS_ALLOW_CREDENTIALS to True. This is important, if you don't do this, no CSRF cookie will be sent with the request
CORS_ALLOW_CREDENTIALS = True
Add the ensure_csrf_cookie decorator to your views handling the JSON API requests:
from django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def myResource(request):
...
Django App for AngularJS - The AngularJS app is served through a Django App in the same project. This Django App is set-up to set a CSRF Cookie. The CSRF token from the cookie is then used for requests to the API (which thus runs as a part of the same Django project).
Note that almost all files related to the AngularJS application are just static files from the Django perspective. The Django App only needs to serve the index.html to set the cookie.
In settings.py for this app (again an extension of the Django project settings.py), set the CSRF_COOKIE_DOMAIN such that subdomains can also use them:
CSRF_COOKIE_DOMAIN = ".mydomain.com"
In views.py, I only need to render the AngularJS index.html file, again using the ensure_csrf_cookie decorator:
from django.shortcuts import render
from django.views.decorators.csrf import ensure_csrf_cookie
# Create your views here.
@ensure_csrf_cookie
def index(request):
return render(request, 'index.html')
Sending requests to the API using AngularJS - In the AngularJS App config set the following $httpProvider defaults:
$httpProvider.defaults.xsrfCookieName = 'csrftoken';
$httpProvider.defaults.xsrfHeaderName = 'X-CSRFToken';
$httpProvider.defaults.withCredentials = true;
Again, take note of the withCredentials, this ensures that the CSRF Cookie is used in the request.
Below I show how you can make requests to the api using the AngularJS $http service and JQuery:
$http.post("http://api.mydomain.com/myresource", {
field1 : ...,
...
fieldN : ...
}, {
headers : {
"x-csrftoken" : $cookies.csrftoken
}
});
Also see ngCookies module.
Using JQuery (1.11.0):
$.ajax("http://api.mydomain.com/myresource", {
type: 'POST',
dataType : 'json',
beforeSend : function(jqXHR, settings) {
jqXHR.setRequestHeader("x-csrftoken", get_the_csrf_token_from_cookie());
},
cache : false,
contentType : "application/json; charset=UTF-8",
data : JSON.stringify({
field1 : ...,
...
fieldN : ...
}),
xhrFields: {
withCredentials: true
}
});
I hope this helps!!