Non-user authentication in Django and Django Rest Framework (DRF)

Charalambos Paschalides
3 min readAug 18, 2021

Let’s imagine that our application needs to be used by some particular users, who are not real users on our database backend. For example, let’s assume we have a medical app used by doctors and patients. And then we need to provide certain access to Hospital administrators to get an overview of their doctors’ and patients’ activities. These hospital administrators are not and cannot become system users.

The problem becomes complex since Django’s authentication flow revolves around the concept of a user, and plenty of assumptions rely on the user being available.

How would you approach authentication and authorization in that case? How would this non-system users access the app, and how would the app handle them?

Authentication and authorization

But first let’s see an overview of authentication and authorization in Django/DRF. It’s important to understand the request flow in order to come up with a good solution. The overview here mentions those Django components that are relevant to authentication.

Authentication consists of the synergy between middleware and view functionality.

Django’s SessionMiddleware

Django’s SessionMiddleware reads the session cookie for all incoming requests and sets it for all outgoing requests that don't have it. When reading the session cookie on an incoming request, the middleware sets the request.session parameter to refer to a SessionStore object which wraps the session.

The SessionMiddleware runs for all incoming requests. Even anonymous requests get a session that refers to AnonymousUser. Even requests to an API that uses Token-based authentication, and are therefore anonymous from a session perspective, do get the request.session object as well as the response cookie.

Django’s AuthenticationMiddleware

Django’s AuthenticationMiddleware sets the request.user parameter to refer to the User instance that the SessionStore object pointed to by request.session, refers to.

DRF View

  1. Request class

One of the very first things a DRF view does is wrap the request object around its very own Request class. Among others, this overrides the request.user object, essentially cancelling out the AuthenticationMiddleware's earlier action, and rendering that middleware useless.

Therefore, as far as API requests go, the AuthenticationMiddleware is entirely redundant.

2. Authentication

The view performs authentication by simply invoking request.user. This, essentially calls the authenticate method on the necessary authentication classes and sets the value of request.user to the User instance returned by the authenticator class.

In other words, one authentication class sets the request.user object to the User instance that made the request. The user instance could, for example, be the one that the Token corresponds to.

It’s worth mentioning that authentication classes are either defined project-wide using the REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] parameter, or on class level, using the authentication_classes parameter.

3. Permissions

The view checks permissions, ensuring that the request.user has the necessary access permissions or role to proceed.

Solution

Now that we have a solid understanding of authentication and authorization flow in Django/DRF, let’s figure out a solution to the problem mentioned earlier.

It should be obvious by now, that most of our logic should live in the authentication class, whose goal in general is to pair the incoming request with a user. In our case, of course, there is no such thing as a user, but rather a hospital.

Let’s assume that the Hospital is indeed one of the data models in our application. The solution that I've come up with, involves an extension of the AnonymousUser class that can refer to a Hospital instance. This way we get an in-memory User instance without littering the database, and the instance still gets to refer to the Hospital that the request is made on behalf of.

This way, any time the authenticate method runs successfully, the incoming request will get the request.user object that contains the request.user.hospital parameter which indicates the Hospital instance that talks to our backend.

From that point on, request.user.hospital may be used throughout the request life cycle.

Conclusions

As you can see the view’s querying logic is based on the concept of Hospital and we've accomplished this with quite a few benefits:

  • Without littering the database with dummy users.
  • Without changing our data model to accommodate new types of users (hospital administrators).
  • Using the regular Django/DRF authentication flow.
  • We are able to plug in our authentication and permission classes into any view and work as we always do.

--

--