Django CORS API from AngularJS

All we need is an easy explanation of the problem, so here it is.

I have enabled CORS in Django, with “django-cors”:

https://github.com/ottoyiu/django-cors-headers

After following the installation steps here, I have set the following:

CORS_ORIGIN_ALLOW_ALL = False

CORS_ORIGIN_WHITELIST = (
    'http://localhost:8000'
)

The django app runs on http://locahost:3000

My frontend is an Angular app, which runs on “http:/localhost:8000”, and I have done the following changes to communicate with the django app.

RestangularProvider.setBaseUrl('https://localhost:3000/');
[Using Restangular for resource APIs]

When I call the GET API, the “OPTIONS” pre-flight call happens, and I get the following error:

XMLHttpRequest cannot load http://localhost:3000/users. Credentials flag is ‘true’, but the ‘Access-Control-Allow-Credentials’ header is ”. It must be ‘true’ to allow credentials. Origin ‘http://localhost:8000‘ is therefore not allowed access.

Looking at the documentation, I understood that I needed to set certain headers which the server would expect as a part of the call. So, I added the following:
RestangularProvider.setDefaultHeaders({“x-requested-with” : ‘XMLHttpRequest’});

However, on making this change, I am getting another error, which I am unable to resolve:
XMLHttpRequest cannot load http://localhost:3000/users. Response for preflight is invalid (redirect)

Note: The request/response headers are as follows:

General:
Remote Address:127.0.0.1:3000
Request URL:http://localhost:3000/users
Request Method:OPTIONS
Status Code:301 MOVED PERMANENTLY

Response Headers
Access-Control-Allow-Headers:x-requested-with, content-type, accept, origin, authorization, x-csrftoken, user-agent, accept-encoding
Access-Control-Allow-Methods:GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Origin:http://localhost:8000
Access-Control-Max-Age:86400
Content-Type:text/html; charset=utf-8
Date:Thu, 17 Dec 2015 11:10:16 GMT
Location:http://localhost:3000/users/
Server:WSGIServer/0.1 Python/2.7.10
X-Frame-Options:SAMEORIGIN

Request Headers
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, x-requested-with
Access-Control-Request-Method:GET
Cache-Control:no-cache
Connection:keep-alive
Host:localhost:3000
Origin:http://localhost:8000
Pragma:no-cache
Referer:http://localhost:8000/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.86 Safari/537.36

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

I finally managed to resolve the problem. The problem was because of improper API naming.

My colleague had named the API as follows:

url(r'^users/', views.user_details)

And when I would call “/users”, then because of django’s APPEND_SLASH setting, it was doing a permanent redirect to “/users/”. Here’s what the django documentation says about APPEND_SLASH:

When set to True, if the request URL does not match any of the patterns in the URLconf and it doesn’t end in a slash, an HTTP redirect is issued to the same URL with a slash appended. Note that the redirect may cause any data submitted in a POST request to be lost.

The APPEND_SLASH setting is only used if CommonMiddleware
is installed.

Of course, the simplest (and best) way of resolving this problem, is to update the API url by removing the slash, i.e.

url(r'^users', views.user_details)

Then it would match the URL directly, and no redirect will be issued.

However, in case someone really wants to keep the trailing slash in the API, then you can still make it work, by adding the following code in AngularJS:

resourceProvider.defaults.stripTrailingSlashes = false;
RestangularProvider.setRequestSuffix('/'); 

The above is however not recommended, but since it came out of my experiments, I am sharing it anyways.

Method 2

I dug around in https://github.com/ottoyiu/django-cors-headers/blob/1e7bf86310e54cf2520c1b197d4e54bf80004df3/corsheaders/middleware.py and found that one can use

CORS_ALLOW_CREDENTIALS = True

in django settings.py

this will set the Access-Control-Allow-Credentials response header to True

I now see it’s even documented: https://github.com/ottoyiu/django-cors-headers

That solved it for me

Method 3

For what it worth, it seems the django-cors documentation only uses hostnames (and not the full URL) in the django settings, like:

Example:

    CORS_ORIGIN_WHITELIST = (
        'google.com',
        'hostname.example.com'
    )


Default:

    CORS_ORIGIN_WHITELIST = ()

Maybe you can try with a simple thing, like localhost, then try if if works, then add the port number and check if it still works ?

Method 4

This CORS creating problems as if you set $httpProvider.defaults.withCredentials = true; in your angular app. i tried many times with many solution but after long search this is my solution for you and i am sure its work for you

just add this code in your Django apache config file its may be httpd.conf file (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> 

or if their is inbuilt code then find your folder directory in that file and only this code in it

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

and also you need to change angular app config with following

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;
   }]);

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply