Forums

Django CsrfViewMiddleware setting without HTTPS

Hi everyone, since it's my first post, first of all cheers for the platform. I managed to run a simple django app with very few headaches.

The app is at a very early stage, and offers only a couple of functionalities, none of which requires user log-in, thus I didn't set up any https certificate, nor a private domain, and it is running on myusername.pythonanywhere.com

My question is about CSRF. I did follow the ajax-set suggested by the most recent django documentation (https://docs.djangoproject.com/en/1.11/ref/csrf/#ajax), and it has worked correctly for a long time in my local env (that is, in my laptop and via 127.0.0.1:8000). In simpler words, every post request works fine there.

Now, the app is running fine also after deployment in this host, but I needed to revert many of the steps in the django "deployment checklist". Mainly I removed the CsrfViewMiddleware from my settings, and also set the related settings to False.

I would like to understand the source of the problem, and if all I need is to set up a https certificate when I'll upgrade the app to its full version.

Cheers!

When you're running in a free account, using the pythonanywhere.com subdomain, you actually have HTTPS set up for free -- just go to https://yseo.pythonanywhere.com to see it.

What errors were you getting that made you remove the Csrf stuff?

Actually I rightly guessed the "problem" (hard to call it this way) was a default https when using the *.pythonanywhere.com domain. The error was some 40x forbidden saying "You haven't set the csrf token" (luckily shown even if my DEBUG property was False). By exclusion I understood it should be caused by some extra protection (and not by a mistake of mines, since I used the ajax-setup suggested by django docs, working for good during local dev), so I disabled the above mentioned middleware and it worked. I am not sure at all is the best solution, though.

In any case, I am mostly interested in knowing if setting up a https certificate on my own domain solve the issues or not, because I am planning to pull the entire app and upgrade shortly. If nobody has previous experience of a problem like this then I guess I'll have to try and see...

Thanks!

The behaviour should be the same, CSRF-wise, regardless of whether you're using HTTPS with a pythonanywhere.com subdomain or on your own custom domain with its own cert.

Oh, I see, but then I am quite confused on what the cause of the error can be. I am triggering a fairly simple Ajax post request, and before that I Ioad a JavaScript file with all the code suggested by the django docs. And again, it's totally working fine in local. Do you have any suggestions about how to understand more?

Is there some code I could look at? As it's JS, perhaps just a URL on your site?

Oh, thanks a lot for asking! Just go on yseo.pythonanywhere.com/ideas/, type some word (for instance, "movies"), then click "Discover". This will trigger the post request. Tha JS will be loaded into baseajax.js

Is there anything I can do if the JS is not enough, to understand the problem?

That's working as far as I can see. The ajax request is going out and a 200 response with some json data is coming back.

Yes, It is, but only because I deactivated the CsrfViewMiddlerware (see previous messages), and this is not supposed to be right, isn't it?

When I go to that view you mentioned and click discover, and look at the network request using browser dev tools, it doesn't submit a CSRF token with the post.

So I am doing some mistake, but never had csfr problem in development. Isn't the following code suppose to generate a token for any post request?

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('csrftoken');

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
    beforeSend: function(xhr, settings) {
        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
            xhr.setRequestHeader("X-CSRFToken", csrftoken);
        }
    }
});

btw if you check your network requests, getCookie is returning null, probably because you didn't get a csrf token from the server.

do you have CSRF_USE_SESSIONS set to False?

Also from the django docs:

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().

I just triple-checked everything, and I cannot see the problem. The view decorator is there.

I think I traced it down, and it seems a problem of the GET rather than the POST. When that page is asked at first, the response does not contain the cookie (which should be then send via the post).

For some reason, when I run it locally, the first response also contains the cookie. But I can't figure out the difference.

It seems I solved the problem, by setting

CSRF_COOKIE_SECURE = False (was True before)

I am not sure whether I misunderstood something in the django docs, or one of your previous message, but that setting should be True if the website has https, and I understood I got it for free by using a sub-domain.

Anyway, it seems fine now. Let me say the users' support is awesome in this app. Thanks

maybe you have certain parts of your page that are doing http (not https) requests or something? we do offer ssl certs for your yseo.pythonanywhere.com domain, but you still need to make sure you force https

the csrf token is returned correctly by the "get" view, I can see it in the network tab "cookies". It's just that document.cookie is always empty ("") for some reason.

I installed django-sslserver to simulate a https in my localhost, and everything works fine after setting CSRF_COOKIE_SECURE back to True.

Hi there, shall we try and create a minimal repro? If I understand correctly, you're saying that the combination of:

  • using https on pythonanywhere
  • setting CSRF_COOKIE_SECURE=True

results in document.cookie being empty for GET requests to a django site, when you expect to see a csrf token in there. is that correct?

Yes, it is correct (document.cookie is empty after the GET response arrives with 200-OK)

Boundary pieces of information (not sure how relevant): - the csrf_cookie is correctly sent from the view and it arrives to the browser, I can see it in the network tab when I check the response - setting CSRF_COOKIE_SECURE=False does the job on pythonanywhere (that is, document.cookie contains csrf_token) - localhost with CSRF_COOKIE_SECURE=True and django-sslserver works fine as well

I may try to publish a minimal version of my app, if I can reproduce the problem with it (maybe I can't :D). Would that help, or you prefer to do by yourself?

what's your setting for CSRF_COOKIE_HTTPONLY?

a minimal repro would definitely help, if you can make one! pop it on github and i'll be able to try it out...

I have explicitly set it to False, although it's the default behavior :D

I'll see if I can prepare a minimal working example. I mean, not-working

Okay, I was able to reproduce the symptoms, I hope the cause as well :)

It's available here anywheretest.pythonanywhere.com

If you don't mind I'd like to give you the password for the anywheretest account, so you can check also the configuration, not just the django code. If not, then find it here https://github.com/pgrandinetti/anywheretest

Let me know :)

That looks like it's doing exactly what it's supposed to. The cookie is marked secure and so it is not sent across an insecure connection. When I use https, the secure cookie is sent and the request is fine. What exactly were we trying to determine from this?

how can change my already configured custom domain name back to the pythonanywhere.com postfix domain name, since i don't have SSL certificate with my current domain and don't want to purchase it right now??

if you change your custom domain name back to the username.pythonanywhere.com domain name, the ssl cert should just automatically reset. (we already provide a cert for the username.pythonanywhere.com one)