4
votes

How to deal with the independent home website and admin website CSRF issue in one browser?

We have written a home website frontend and an admin website frontend for it (there are two frontends), using JavaScript in the frontend.
Because both sites are large, we have put them as vhost in Nginx. They are two sites. I use Python (Django) to write one backend, both sites invoke one backend.

The home website uses Nginx's default.conf, the admin website uses vhosts/admin.conf.

This is the test domain:

http://www.ajw123.xyz as the home website.
http://admin.ajw123.xyz as the admin website.

My trouble is when I use an account login on the home website in the browser:

enter image description here

Then I use the account to login to the admin website (or other accounts), the CSRF Token Error is thrown:

enter image description here

You see the csrftoken both are:

csrftoken=L5bRGEXDvW9dJaXsanLlMTOrxxGpxJCw6vji1zQtjzYrskOq0FBjQtfkhvFKFDmj; 

In the preview:

enter image description here

CSRF Failed: CSRF token missing or incorrect.

I use Django-Rest-Framework for the rest APIs, our frontend colleagues write two sites (one is normal user frontend and backend site, the other is admin's backend site), and both are using my rest APIs.

3
Use different cookie name on each onecharlietfl
@charlietfl How to do with that? do you mean in Nginx?aircraft
In whatever Django module that manages the csrf. i'm not a django dev but have done this in other environmentscharlietfl
@charlietfl Thank you anyway.aircraft
This is very important information you omitted in your question. So basically your Django project is a REST API. The frontends (home and admin) are clients consuming the API. Right? Did you read this page of the documentation?cezar

3 Answers

4
votes

The question is in my opininon an example of XY problem. In the following text I'll come back to my claim and explain it.

The OP has written a REST API using Django REST Framework. Omitting this information initially led to very low attention. After including this information the thing became much clearer.

Let's first recall some basics about REST APIs. A REST API is language agnostic. It doesn't care in which language the clients are written, and also the clients don't care in which language the API has been written. The REST API can be accessed (consumed) in different ways: from the command line using curl; from a script written in any programming language; from a browser using (most likely) JavaScript (or a JavaScript framework).

Since there are two websites consuming the API, the OP wanted to provide them access to the API. The obstacle that has arisen was CSRF (Cross Site Request Forgery).
Django has implemented CSRF protection with the use of CSRF tokens. This means we protect our website from requests from other websites, where usually a form can be posted to our website.

In the actuall case the clients are two different websites hosted on different domains, so the requests coming from them are from a different site. What the OP really wants to know is:
"How to grant or restrict access to other websites consuming my API?"
and not:
"How to deal with the CSRF issue?"

The official documentation of Django REST Framwork has a page dedicated to this issue:
Working with AJAX, CSRF & CORS

This is the section for CORS:

Cross-Origin Resource Sharing is a mechanism for allowing clients to interact with APIs that are hosted on a different domain. CORS works by requiring the server to include a specific set of headers that allow a browser to determine if and when cross-domain requests should be allowed.

The best way to deal with CORS in REST framework is to add the required response headers in middleware. This ensures that CORS is supported transparently, without having to change any behavior in your views.

Otto Yiu maintains the django-cors-headers package, which is known to work correctly with REST framework APIs.

I'll emphasise the first sentence:

Cross-Origin Resource Sharing is a mechanism for allowing clients to interact with APIs that are hosted on a different domain.

Exactly that is the case. The OP wants to allow clients to interact with his API that is hosted on a different domain.
The last sentence recommends the use of django-cors-headers which is the solution of the problem.
All further details about the use of the app can be found in its documentation.

3
votes

CSRF token is essentially a cookie which is retrievable.
By default, the name of this cookie is csrftoken for every django app.

You need to change the name of at least one of the cookies with the CSRF_COOKIE_NAME setting (in your settings.py).

Then your colleagues can retrieve that cookie with the following AJAX call:

// using jQuery
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('name_of_your_token');

For more complicated uses check the documentation: https://docs.djangoproject.com/en/2.0/ref/csrf/


Multiple Django sites on the same domain - CSRF fails
3
votes

Thanks @cezar and @JohnMoutafis, I read the doc of CORS:

Cross-Origin Resource Sharing is a mechanism for allowing clients to interact with APIs that are hosted on a different domain. CORS works by requiring the server to include a specific set of headers that allow a browser to determine if and when cross-domain requests should be allowed.

Then I installed the django-cors-headers, and use it.

I set the CORS_ORIGIN_WHITELIST:

CORS_ORIGIN_WHITELIST = (
    'http://10.10.10.102:8000', # normal user site
    'http://10.10.10.102:8080', # admin backend site
)

Now it works fine.

Thank you again.