Django internals: authentication
Django’s session and authentication frameworks are designed to Just Work™, and can seem pretty magical. Like the rest of Django, though, these parts aren’t magic — just Python. So let’s take a look at the internals of sessions and authentication and see how the whole thing works.
Our journey begins with the session
middleware.
You’ll see that the process_request
method
is pretty simple: it looks up the session engine
setting,
looks for a session key in the request’s cookies, and then sets
request.session
to a SessionStore
instance.
I’ll skip over the details of SessionStore
(see the implementations if
you want to get more details) other than to note that
SessionStore`
just behaves like a dict
, and that the data you
store there is automatically persisted for you. All that the user
receives is a session cookie, an opaque key identifying the session. You
can read more about the technical details of the session
framework in Chapter 14
of The Django Book.
I’ll assume from here on out that you’re using the database session backend since that’s the easiest to poke at.
So now we hop over to the auth
middleware.
The auth middleware is similarly simple: it sets request.user
to a
LazyUser
[1]. Like request.session
, request.user
is
lazy: the user won’t be loaded until request.user
is accessed.
When request.user
is accessed – often by something like the
@login_required
decorator – LazyUser
calls
django.contrib.auth.get_user()
, passing in the request; get_user()
pulls info out of the session and, if the user is authenticated, returns
the appropriate User
instance.
So what’s the data that’s stored in the session? Let’s take a look:
>>> from django.contrib.sessions.models import Session
>>> s = Session.objects.get(pk='d638d3e640d2133c8cc0b73d0e88c6b3')
>>> s.get_decoded()
{'_auth_user_backend': 'django.contrib.auth.backends.ModelBackend',
'_auth_user_id': 1}
So you can see that a properly authenticated user gets two pieces of information stored in the session: (a) which backend was used to authenticate that user and (b) what the user’s ID is.
So how does that information get there?
Most of the time, a user is going to get logged in from some view.
Django ships with such a view; it’s django.contrib.auth.views.login
.
There’s a fair bit going on there that’s related to processing the view
and request itself; the important details are the steps you need to take
to log in a user:
- Given a dict of credentials, call
django.contrib.auth.authenticate(**credentials)
(i.e.authenticate(username='jacob', password='mypass')
).authenticate()
returns theUser
object, if successful, or None if the credentials are incorrect. - Given a properly authenticated user, call
django.contrib.auth.login(request, user)
. This is what stores the backend and user ID info in the session.
The final step in the authentication dance happens back in the session
middleware; SessionMiddleware.process_response()
will notice that
request.session
has been modified (in this case by login()
) and save
the altered data in the session.
[1] | Technically, the auth middleware sets request.__class__.user to LazyUser() , which is a descriptor – you’ll recall that descriptors may only be class attributes. |