La autenticación en django funciona a través de dos middleware dependientes, SessionMiddleware y AuthenticationMiddleware. Básicamente, cuando el usuario hace un login exitoso, se guardan tres valores en la sesión:

  • _auth_user_id. PK del objeto User

  • _auth_user_backend.Dotted path del backend de autenticación. Por defecto: django.contrib.auth.backends.ModelBackend

  • _auth_user_hash. Hash de la contraseña del usuario

El hash es la representación hexadecimal (string) de un hasheado con sal de la contraseña del usuario:

1
2
3
4
5
6
7
8
def get_session_auth_hash(self):

    """
    Return an HMAC of the password field.
    """

    key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
    return salted_hmac(key_salt, self.password).hexdigest()

En todos los requests AuthenticationMiddleware es el encargado de establecer cuál es el usuario request.user. Para ello llama a la función get_user del módulo django.contrib.auth y lo que hace es comprobar que la contraseña hasheada guardada en la sesión es la misma que se deriva de hacer el mismo cálculo sobre la guardada en la base de datos. Para ello usa la función constant_time_compare del módulo django.utils.crypto. Se usa esta función para que el tiempo que tome la comparación sea constante y se minimice la posibilidad de un timing attack. Si la comparación no es correcta establece como valor de request.user AnonymousUser.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# django/contrib/auth/middleware.py

def get_user(request):

    if not hasattr(request, '_cached_user'):
        request._cached_user = auth.get_user(request)
    return request._cached_user

class AuthenticationMiddleware(MiddlewareMixin):

    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE%s setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        ) % ("_CLASSES" if settings.MIDDLEWARE is None else "")
        request.user = SimpleLazyObject(lambda: get_user(request))

Ver tb:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# django/contrib/auth/init.py

def get_user(request):
    """
    Return the user model instance associated with the given request session.
    If no user is retrieved, return an instance of `AnonymousUser`.
    """
    from .models import AnonymousUser
    user = None
    try:
        user_id = _get_user_session_key(request)
        backend_path = request.session[BACKEND_SESSION_KEY]
    except KeyError:
        pass
    else:
        if backend_path in settings.AUTHENTICATION_BACKENDS:
            backend = load_backend(backend_path)
            user = backend.get_user(user_id)
            # Verify the session
            if hasattr(user, 'get_session_auth_hash'):
                session_hash = request.session.get(HASH_SESSION_KEY)
                session_hash_verified = session_hash and constant_time_compare(
                    session_hash,
                    user.get_session_auth_hash()
                )
                if not session_hash_verified:
                    request.session.flush()
                    user = None

    return user or AnonymousUser()