Search code examples
pythondjangopersona

Django reload and re-login after logout


Django v1.7

I've been working through this book (which is fantastic, btw), and I thought I had everything working. All of the tests (functional and unit), but for some reason, every time I hit the logout button, I am immediately logged back in. I am using a custom authentication backend (the one given in the book), which uses Mozilla Persona, as described in the book (the chapter in the link).

I have seen a few similar posts, but none of the solutions helped.

Behaviour:

  1. Logging in with bad credentials will continually refresh the page, which appears to query Persona each time (the Persona error: Persona says no. Json was: {'status': 'failure', 'reason': 'audience mismatch: domain mismatch'} --I accidentally discovered this by visiting 127.0.0.1:8000 instead of localhost:8000*). I do not know if Persona is queried each time or if the message is kept after each page refresh.
  2. The initial login appears to work correctly. The persona pop-up appears and goes through the steps before closing and reloading the page.

  3. After logging out, the page will refresh, sending post information and logging back in: [06/Nov/2015 21:25:20] "GET /accounts/logout HTTP/1.1" 302 0 [06/Nov/2015 21:25:20] "GET / HTTP/1.1" 200 795 [06/Nov/2015 21:25:21] "POST /accounts/login HTTP/1.1" 200 2 The logout is redirected back to the root page '/'.

  4. This behaviour is persistent through restarting the server and the web browser. If I stop the server and close the browser and reopen both (entering in the web address again), the page is already logged in.

  5. This behaviour also persists through different git branches. I am not sure when it started (because the tests still pass), but I know it worked previously. Every branch I checked has the same issue, which makes me think it's something to do with the cache or installation.

  6. The behaviour also persists through removing all __pycache__, migrations, and the database itself.

  7. The behaviour persists through clearing the cache. (EDIT/UPDATE: I'm still writing this, so it's not technically an update...I had previously emptied the cache only for 'Today' (Firefox), which had no effect; however, I just cleared everything, and it seems to have solved the problem. I need to do more testing; I'll update once I'm sure.)

*I do know that Persona says to use the ip over localhost, but it doesn't seem to make a difference.

Here's a minimal working example:

placeholder.html (NOTE: I put the scripts in the body.

{% load staticfiles %}

<html>
<head>
    <title>placeholder</title>
</head>

<body>
    {% if user.email %}
        <h3>User: {{ user.email }}</h3>
        <div class="item">
            Logged in as <b>{{ user.email }}</b>
        </div>
        <div class="item">
            <a class="ui button" id="id_logout"
                href="{% url 'logout' %}">Log out</a>
        </div>
    {% else %}
        <div class="item">
            <a class="ui button" id="id_login" href="#">Sign in</a>
        </div>
    {% endif %}

    <script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
    <script src="https://login.persona.org/include.js"></script>

    <script>
    var initialize = function(navigator, user, token, urls) {
      console.log('called initialize');
      $('#id_login').on('click', function() {
        navigator.id.request();
      });
      navigator.id.watch({
        loggedInUser: user,
        onlogin: function(assertion) {
          $.post(
            urls.login,
            {'assertion': assertion, 'csrfmiddlewaretoken': token}
          )
            .done(function() { window.location.reload(); })
            .fail(function() { navigator.id.logout(); });
        },
        onlogout: function() {}
      });
    };

    window.MyModule = window.MyModule || {
      Accounts: {
        initialize: initialize
      }
    };
    </script>

    <script>
    /*global $, MyModule, navigator */
    $(document).ready(function () {
        var user = "{{ user.email }}" || null;
        var token = "{{ csrf_token }}";
        var urls = {
            login: "{% url 'persona_login' %}",
            logout: "TODO",
        };
        MyModule.Accounts.initialize(navigator, user, token, urls);
    });
</script>

</body>
</html>

accounts/views.py (NOTE: I was using Django native auth.logout, which you will see in urls.py)

from django.contrib.auth import authenticate, login
from django.contrib.auth import logout as auth_logout
from django.http import HttpResponse
from django.shortcuts import redirect

def persona_login(request):
    user = authenticate(assertion=request.POST['assertion'])
    if user is not None:
        login(request, user)
    return HttpResponse('OK')


def logout(request, next_page):
    auth_logout(request)
    return redirect('/')

urls.py

from django.conf.urls import patterns, include, url
#from django.contrib import admin

urlpatterns = patterns(
    '',
    # url(r'^admin/', include(admin.site.urls)),

    url(r'^accounts/', include('apps.accounts.urls')),

    url(r'^$', 'apps.projects.views.placeholder_view',
        name='placeholder'),
)

accounts/urls.py (NOTE: I was using the native logout--neither method works)

from django.conf.urls import patterns, url
# from django.contrib.auth.views import logout

urlpatterns = patterns(
    '',
    url(r'^login$', 'remsci.apps.accounts.views.persona_login',
        name='persona_login'),
    # url(r'^logout$', logout,
    #     {'next_page': '/'}, name='logout'),
    url(r'^logout$', 'remsci.apps.accounts.views.logout',
        {'next_page': '/'}, name='logout'),

authentication.py (NOTE: This is directly from the book)

import requests

from django.conf import settings
from django.contrib.auth import get_user_model

User = get_user_model()

PERSONA_VERIFY_URL = 'https://verifier.login.persona.org/verify'

import logging
log = logging.getLogger(__name__)

class PersonaAuthenticationBackend(object):

    def authenticate(self, assertion):
        response = requests.post(
            PERSONA_VERIFY_URL,
            data={'assertion': assertion, 'audience': settings.DOMAIN}
        )

        if response.ok and response.json()['status'] == 'okay':
            email = response.json()['email']
            try:
                return User.objects.get(email=email)
            except User.DoesNotExist:
                return User.objects.create(email=email)
        else:
            log.warning(
                'Persona says no. Json was: {}'.format(response.json()))

    def get_user(self, email):
        try:
            return User.objects.get(email=email)
        except User.DoesNotExist:
            return None

EDIT/UPDATE Again, while I was writing this. It would appear that this problem has already been solved here. I have a physical copy of the book, but going back through and verifying everything using the online copy, I just found the link pointing to this code. I'll leave this up, just in case someone else is having this problem...or, I'll remove it if I can find the same problem pointing to the same solution.


Solution

  • From above:

    It would appear that this problem has already been solved here. I have a physical copy of the book, but going back through and verifying everything using the online copy, I just found the link pointing to this code. I'll leave this up, just in case someone else is having this problem...or, I'll remove it if I can find the same problem pointing to the same solution.