Search code examples
pythondjangodjango-rest-frameworkdjango-authenticationdjango-users

Do `Users` need to exist in Django for SSO?


Basically, I am implementing the following functionality where the Front end will send signed JWTs to the backend. If the backend is able to decode and hence validate the Token (i.e. the signature, claims, audience, etc), then it will give access to the protected API resources:

enter image description here

Now the users already exist in Azure AD (so I don't want to have to create/manage users in the Django DB as well). So essentially all I want to do is protect the Django Restful API endpoints with validated Access Tokens. I was looking at Custom Authentication (by extending rest_framework.authentication.BaseAuthentication) but it seems that the authenticate method expects a User to be matched (Hence Users to exist) and returned for a successful authentication (I would like to just pass if JWT validation is a success or raise Exception is there is an error).

Django docs:

Either way, authenticate() should check the credentials it gets and return a user object that matches those credentials if the credentials are valid. If they’re not valid, it should return None.

I have achieved my desired flow (i.e. having no Django Users) using method_decorator but this does not seem like a correct pattern for authentication:

class ComponentViewSet(viewsets.ModelViewSet):
    queryset = Component.objects.all()
    serializer_class = ComponentSerializer

    @method_decorator(validate_jwt)
    def dispatch(self, *args, **kwargs):
        return super().dispatch(*args, **kwargs)

Django docs also state that:

The Django admin is tightly coupled to the Django User object. The best way to deal with this is to create a Django User object for each user that exists for your backend (e.g., in your LDAP directory, your external SQL database, etc.).You can either write a script to do this in advance or your authenticate method can do it the first time a user logs in.

I have also done a separate implementation of the Custom Authentication by creating Users on the fly in the authenticate method just as the documentation recommended.

So my question is, is having Users exist in Django a strongly recommended design (or must according to the docs) even when you have a centrally managed Authentication system like LDAP/AD etc? Or is it also common not to use the Users model and use some other kind of implementation (for example the method decorator I used) when you don't intend to hold any kind of User information in the backend because I see it as redundant? Are there any advantages of duplicating a users database in the Django backend if it is strongly recommended. And do these users also require passwords if they extend django.contrib.auth.models.User?


Solution

  • Rather than needing to use a decorator on every method, just make your own authentication backend. When a view calls authenticate(), Django calls all backends specified in AUTHENTICATION_BACKENDS until one doesn't return None.

    In regards to storing users, Django assumes by default that you want to store session data in the backend, to not require login. If you're fine with reauthenticating on every request, then you can feasibly just return a new instance of BaseUser object, without saving to the database, although other Django functionality might break in unexpected ways.

    You might want to explore creating your own user model based on AbstractUser or even AbstractBaseUser. If you're not getting a new token on every request, it makes sense to store the token in the user model, along with a when_authenticated, time_to_expiry, etc.

    A new user OR a returning user who's when_authenticated + time_to_expiry < now() access token will be validated with the auth server. On success, update the necessary time fields, along with current_access_token. Subsequent requests within your refresh window merely have to do a fast equality check, as opposed to another request to the server.

    This will save your auth server from lots of requests, speed up your API (no auth server validation on every call), and still let you use some of the awesome functionality Django lets you have with users.