Search code examples
pythondjangopython-3.xdjango-authenticationdjango-login

Django Forbidden 403 Error "CSRF token missing or incorrect" when trying to log in when already logged in


So I am using all built in Django, I don't have my own login view.


First I'll describe how the scenario is created that causes this error:

So someone is logged in and has multiple tabs open and then they log out. Eventually the other tabs will be directed to the accounts/login page either by themselves or the page is refreshed or a link is pressed, it doesn't really matter how. The point is this person now has multiple tabs that are all on the accounts/login screen and no one is authenticated/logged in.

So then this person successfully logs in in one of the tabs. They then go and to one of the other tabs, which are still on the accounts/login page (even though a session is already created).

If this person then tries to log in as the person logged in, or really anyone, in fact even if the username and password are empty and they simply click the login button, the 403 Error CSRF token missing or incorrect gets thrown.

(I hope this makes sense)

Anyways, how can I fix this?

And this is my html template if that helps:

{% extends 'base.html' %}
{% load crispy_forms_tags %}

{% block content %}
  <div class='panel panel-primary' style='margin: 0 auto; max-width: 360px;'>
    <div class='panel-heading'>
      <h3 class='panel-title'>Login</h3>
    </div>
    <div class='panel-body'>
      <form action='{% url 'login' %}' novalidate='novalidate' role='form' method='post'>
        {% csrf_token %}
        {{ form|crispy }}
        <input type='hidden' name='next' value='{{ next }}' />
        <div class='form-group' style='margin-bottom: 0;'>
          <p>
            <small>
              By using this site, you agree to our
              <a href='{% url 'privacy-policy' %}'>privacy policy</a>
              and
              <a href="{% url 'terms-of-use' %}">terms of use</a>.
            </small>
          </p>
          <button class='btn btn-primary' type='submit'>Login</button>
        </div>
      </form>
    </div>
  </div>
{% endblock %}

Edit:

I would also like to add that if I create the same scenario and instead of immediately trying to log in, instead I reload the page just once, the 403 error doesn't happen.

I just want to be able to prevent users from ever running into an error while using the site (outside of typing things directly into the search bar of course). One idea could be that right when login is clicked it will refresh the csrf token the same way a page refresh would, is this possible and a good idea?


Solution

  • Well, it appears I've found a solution that doesn't exactly solve the error directly but it does avoid the situation where the error would happen. The behavior is a little bit different, but it works really well for me and I assume it will be the same most of the time for others.

    So first, I reroute away from the login page whenever the user is already logged in. I accomplish this with creating my own login view:

    class LoginView(LoginView):
        def dispatch(self, request, *args, **kwargs):
            if request.user.is_authenticated:
                return HttpResponseRedirect(request.GET.get("next") or "/")
            else:
                return super().dispatch(request, *args, **kwargs)
    

    Second, I made it so every time the window becomes "focused" (i.e. you switch to the tab) a simple ajax request is sent to check if the user is authenticated, if they are, the window will be reloaded, thus bringing them to the correct page.

    To accomplish this I added this javascript to the login.html page:

    {% block extrascripts %}
      {{ block.super }}
      <script>
          "use strict";
    
          window.onfocus = function () {
            $.ajax({
              url: "/accounts/status/",
              success: function(result, status, xhr) {
                if (result.is_authenticated) {
                  window.location.reload();
                }
              }
            });
          };
      </script>
    {% endblock %}
    

    And added this view:

    @method_decorator(csrf_exempt, name="get")
    class StatusView(View):
        def get(self, request, *args, **kwargs):
            return JsonResponse({"is_authenticated": request.user.is_authenticated})
    

    And I also updated the URLs as well of course.


    I'll also add that if you don't want to be redirected away from login page when already authenticated, you can leave that code out and it should still fix the error, considering I wasn't having the error when I would reload the page first before trying to log in.