Categories
DevOps django

Authenticated Functional Tests with Selenium and Django

In the Test Driven Development Book for Python and Django by Harry Percival called Obey The Testing Goat, there is a chapter about enhancing the functional test base class and adding pre-authentication so you don’t need to login via the login screen with Selenium.

It uses a custom Email Authentication Backend, but I needed to implement this on a standard: django.contrib.auth.backends.ModelBackend.

My First Attempt


    def create_pre_authenticated_session(self, user):
        '''Create an authenticated user quickly'''
        session = SessionStore()
        session[SESSION_KEY] = user.pk
        session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0]
        session.save()
        # visit domain (404 quickest)
        self.browser.get(self.live_server_url + "/404_no_such_url/")
        self.browser.add_cookie(dict(
            name=settings.SESSION_COOKIE_NAME,
            value=session.session_key,
            path='/',
        ))

I ran my functional test and something weird was happening, the cookie was getting killed right after this method is called an going to any page.

So I compared based on the cookie itself compared to one that existed in firefox developer tools.

The difference was the httpOnly thingy. So I added it…


        self.browser.add_cookie(dict(
            name=settings.SESSION_COOKIE_NAME,
            value=session.session_key,
            path='/',
            secure=False,
            httpOnly=True
        ))

Nothing changed, the cookie was still gone.

So then I compared an existing decoded session with the one created via the method above.

To find the decoded session:

$ python manage.py shell
[...]
In [1]: from django.contrib.sessions.models import Session

# substitute your session id from your browser cookie here
In [2]: session = Session.objects.get(
    session_key="8u0pygdy9blo696g3n4o078ygt6l8y0y"
)

In [3]: print(session.get_decoded())
{'_auth_user_id': 'obeythetestinggoat@gmail.com', '_auth_user_backend':
'accounts.authentication.PasswordlessAuthenticationBackend'}

The Session Difference

I noticed there was a difference a working session looked like this:

{'_auth_user_id': '1', '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend', '_auth_user_hash': '6a34097f6dab2a1fc68f262e9e67186d2ad5ba93'}

whereas the one I created looked like this:

{'_auth_user_id': 1, '_auth_user_backend': 'django.contrib.auth.backends.ModelBackend'}

So the _auth_user_hash was a problem. I search the django source and found it in auth.

So I set the hash session key with: session[HASH_SESSION_KEY] = user.get_session_auth_hash()

It then worked.

The Solution


    def create_pre_authenticated_session(self, user):
        '''Create an authenticated user quickly'''
        session = SessionStore()
        session[SESSION_KEY] = user.pk
        session[BACKEND_SESSION_KEY] = settings.AUTHENTICATION_BACKENDS[0]
        session[HASH_SESSION_KEY] = user.get_session_auth_hash()
        session.save()
        # visit domain (404 quickest)
        self.browser.get(self.live_server_url + "/404_no_such_url/")
        self.browser.add_cookie(dict(
            name=settings.SESSION_COOKIE_NAME,
            value=session.session_key,
            path='/',
            secure=False,
            httpOnly=True
        ))