views:

348

answers:

2

I need to create a subdomain based authentication system, like the one 37signals, freshbooks, codebase use. That is, each subdomain of my main application needs to have its own username namespace. I would like to keep as much as possible of the django authentication system.

What is a good way to store the username?

In particular, it should be possible for different users to have the same username as long as their account belongs to a different subdomain.

Some approaches I've considered, for which I can foresee shortcomings:

  • storing some prefix in the username field of the django auth user model.
  • extending the user model according to this.
  • customizing the source of auth to my needs
A: 

I think this may be a good use case for using django.contrib.sites in combination with the second bullet item you mentioned. You could create a CustomUser model like so:

from django.contrib.sites.models import Site

class CustomUser(User):
    """User with app settings."""
    sites = models.ManyToManyField(Site)

Then you could write a custom auth backend to check that the user can sign in to the current subdomain using the supplied credentials. This allows you to have one username for multiple sites (subdomains) without having to hack the internal auth app or store multiple usernames with custom prefixes.

EDIT: you can get the current site by using Site.objects.get_current() and then check to see if the current site is in the user's sites.

You can read more about the sites framework here: http://docs.djangoproject.com/en/dev/ref/contrib/sites/

richleland
but wouldn't the usernames still have to be unique?
rz
bear also in mind that the sites framework doesn't support multiple sites out of a single deployment out of the box, so in order to emulate freshbooks or the like he'd have to make a new settings file and web configuration for each site!
easel
@rz - no you could basically create one CustomUser and then assign multiple sites to them@easel - true, but it you're already writing a subdomain-based project, you're doing a good amount of automated server config anyway. it wouldn't be too hard to write a post-save signal to dynamically generate a settings file that only contains a SITE_ID and modify your server config/dns.
richleland
@richleland: auth.models.User, which is being extended, sets unique=True on the username. So they would still have to be globally unique.
rz
oh i see what you're saying. i wasn't clear at all then - instead of subclassing User, you should create a UserProfile class. then you can use the AUTH_PROFILE_MODULE to extend a user. see http://docs.djangoproject.com/en/dev/ref/settings/#auth-profile-module
richleland
and also - you're not creating a new user for each subdomain, you're just assigning multiple sites to each unique user. so using the sites framework would work. or you could use the AUTH_PROFILE_MODULE approach.
richleland
A: 

I have built this functionality for several sites in the past and have found that your first bullet point is the way to go.

It means that you don't have to make massive change to django auth. What I did was set up a custom authentication backend that abstracts away the way usernames are stored.

auth_backends.py

from django.contrib.auth.backends import ModelBackend

from Home.models import Account

class CustomUserModelBackend(ModelBackend):
    def authenticate(self, subdomain, email, password):
        try:
            user = Account.objects.get(username=u'%s.%s' % (subdomain, email))
            if user.check_password(password):
                return user
        except Account.DoesNotExist:
            return None

    def get_user(self, user_id):
        try:
            return Account.objects.get(pk=user_id)
        except Account.DoesNotExist:
            return None

For this particular project Account was the user model and it just inherited directly from User however you could replace Account with whatever you want.

You have to install the custom auth backend in your settings file:

AUTHENTICATION_BACKENDS = (
    'auth_backends.CustomUserModelBackend',
    'django.contrib.auth.backends.ModelBackend',
)

Then when you call authenticate you need to pass in the subdomain, email and password.

You can also add some other helper functions or model methods that help with making sure that only the user's actual username is displayed, but that is all pretty trivial.

Aaron Vernon