AngularJS + Django Rest Framework + CORS ( CSRF Cookie not showing up in client )

ⅰ亾dé卋堺 提交于 2019-11-26 18:23:38

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!!

Directly from the docs https://docs.djangoproject.com/en/1.9/ref/csrf/#ajax

If your view is not rendering a template containing the csrf_token template tag, Django might not set the CSRF token cookie. This is common in cases where forms are dynamically added to the page. To address this case, Django provides a view decorator which forces setting of the cookie: ensure_csrf_cookie().

Since your application is a single-page application, you can add ensure_csrf_cookie() to the view that is responsible for the initial page load.

So I found my own solution to this, seems to work great.

This is the new snippets of my code:

Backend API LoginView ( added a decorator forcing the csrf token to be added to the body )

class LoginView(APIView):

renderer_classes = (JSONPRenderer, JSONRenderer)

@method_decorator(ensure_csrf_cookie)
def post(self, request, format=None):
    c = {}
    c.update(csrf(request))
    serializer = LoginSerializer(data=request.DATA)

    if serializer.is_valid():
        userAuth = authenticate(username=serializer.data['username'], password=serializer.data['password'])

        if userAuth:

            if userAuth.is_active:
                login(request, userAuth)

                loggedInUser = AuthUserProfile.objects.get(pk=1)
                serializer = UserProfileSerializer(loggedInUser)

                user = [serializer.data, {'isLogged': True}]



        else:
            user = {'isLogged': False}

        return Response(user, status=status.HTTP_200_OK)

    return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

AngularJS Client side ( add token to the request header )

$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;

Server side settings file ( Specificly for django-cors-headers )

First 5 are added by default, but you need to add "X-CSRFToken" to allow such a header from the client to the API using CORS, else the post will be denied.

CORS_ALLOW_HEADERS = (
'x-requested-with',
'content-type',
'accept',
'origin',
'authorization',
'X-CSRFToken'

)

Thats it!

A small update to this solution.

Since AngularJS 1.2.10 you need to set the CSRF cookie for each request type in the client:

$http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers.put['X-CSRFToken'] = $cookies.csrftoken;
$http.defaults.headers['delete']['X-CSRFToken'] = $cookies.csrftoken;

This due the following change that happened between 1.2.9 and 1.2.10 https://github.com/cironunes/angular.js/commit/781287473bc2e8ee67078c05b76242124dd43376

Hope this helps someone!

After So Much Search i landed on this solution and its work form me on local system and also on live web faction server this is my solution for Django users please go to your apache folder located in project then in bin you find

httpd.conf or your server config for php or other users (usually located in a *.conf file, such as httpd.conf or apache.conf), or within a .htaccess. then just add this code

<IfModule mod_headers.c>
SetEnvIf Origin (.*) AccessControlAllowOrigin=$1
Header add Access-Control-Allow-Origin %{AccessControlAllowOrigin}e env=AccessControlAllowOrigin
Header set Access-Control-Allow-Credentials true
</IfModule> 

then in angular js app you just needed to place

angular.module('app', ['ngCookies'])
    .config([
   '$httpProvider',
   '$interpolateProvider',
   function($httpProvider, $interpolateProvider, $scope, $http) {
       $httpProvider.defaults.withCredentials = true;
       $httpProvider.defaults.xsrfCookieName = 'csrftoken';
       $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
   }]).
   run([
   '$http',
   '$cookies',
   function($http, $cookies) {
       $http.defaults.headers.post['X-CSRFToken'] = $cookies.csrftoken;
   }]);

Its Worked for me on Django Angularjs platform.

https://gist.github.com/mlynch/be92735ce4c547bd45f6

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!