[Django]-Django one user across multiple django sites

3👍

There are two solutions to your problem:

  1. Use routing to multiple databases. Django supports different databases for different models (more info on @ https://docs.djangoproject.com/en/1.8/topics/db/multi-db/). So you could route all queries for your auth models to the authentication database. Django documentation already provides such a configuration @ https://docs.djangoproject.com/en/1.8/topics/db/multi-db/#an-example. I’ve not tested this configuration however this will have the problem of not being able to use foreign keys to your user model (since it will be stored in a different database). To resolve this, you could have a UserProxy model (or something similarly named) in all your projects that will just keep the username of the User so you’ll be able to create foreign key relations from your models to the UserProxy. Any user data that would need to be retrieved would be forwarded to the correct User object by the UserProxy.

  2. Use django authentication. Django can be configured to use a number of different authentication methods (check the docs @ https://docs.djangoproject.com/en/1.8/topics/auth/customizing/). For your sites B and C you can configure an authentication backend that uses the database of the administrative site (A) to login. I am using this method succesfully – here how you could do it:

  class RegUsrBackend(django.contrib.auth.backends.ModelBackend):
    def authenticate(self, username=None, password=None):

        try:
            conn = django.db.connections['users']
            cursor = conn.cursor()

            cursor.execute("select pn.password, pn.username, au.first_name, au.last_name from auth_user au where au.username = %s ", [username])
            row = cursor.fetchone()
            if row and check_password(password, row[0]):
                user, created = get_user_model().objects.get_or_create(username = row[1] )
                if user.first_name != row[2] or user.last_name != row[3] :
                    user.first_name = row[2]
                    user.last_name = row[3]
                    user.set_unusable_password()
                    user.save()
                return user
        except:
            return None

As you can see, here I’ve also configured a different users database (named users) and I am issuing a raw query to this database to get the user with the passed username and its password hash. After that, I check if the user exists and has the correct password (using the check_password) and if everything checks, I use get_or_create to either retrieve or create the local instance (i.e the instance of the user in the application’s database) of that user. Finally, before returning the user I check to see if there’s a change in his first or last name to the administrative application and update them in the local one.

Finally, you need to put this backend in the in the AUTHENTICATION_BACKENDS settings of the applications using it.

Using this method you won’t have to use any UserProxy to support foreign keys (since there will exist a local User model) models but I feel that it is a more hackish method than the first one. Also, if the details of a user has been changed in the administrative database the details in the others will be updated when he logs in. Also you could change your backend to something even more exotic, for example instead of querying the database you could create a REST api in your administrative backend and use this to login to the other applications.

Finally, to answer your question about SSO, what I’ve described above is not SSO. SSO means that when a user logs in to a site he won’t have to log in again to the others becase a single session key is kept and shared in all these sites. A common solution for SSO is to use CAS (http://jasig.github.io/cas/4.1.x/index.html) however I don’t have good experience with it (and you’ll need to have another, java based server to host CAS).

Leave a comment