views:

111

answers:

3

I'm trying to set up a custom backend that queries another database, for which I have created a model in the system. It uses its own rules (email instead of username, and a differently salted/hashed password) so I can't use built in authentication. I've set up a custom authentication backend like so:

class BlahBlahBackend:

    def check_password():
        # check password code here
        return true

    def authenticate(self, email=None, password=None):
        import myapp.models.loginmodel
        try:
            person =  myapp.models.loginmodel.People.objects.get(email=email)
            if check_password(password, person.password):
                try:
                    user = User.objects.get(email=email)
                except User.DoesNotExist:
                    username=person.first_name + person.last_name
                    name_count = User.objects.filter(username__startswith = username).count()
                    if name_count:
                        username = '%s%s'%(username, name_count + 1)
                        user = User.objects.create_user(username,email)
                    else:
                        user = User.objects.create_user(username,email)
        except People.DoesNotExist:
            return None

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

I've added BlahBlahBackend as an authentication backend:

AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend', 'socialauth.auth_backends.OpenIdBackend', 'socialauth.auth_backends.TwitterBackend', 'socialauth.auth_backends.FacebookBackend', 'socialauth.auth_backends.BlahBlahBackend', )

As you can see, I'm also using some pre-existing auth backends that are also in socialauth.

I have a submission form that points to the following view:

def blahblah_login_complete(request):
    email = request.POST.get('email')
    password = request.POST.get('password')
    user = authenticate(email,password)
    # if user is authenticated then login user
    if user:
        login(request, user)
    else:
        return HttpResponseRedirect(reverse('socialauth_login_page'))

However, when I try to login in this way, it seems like one or more of the other backends are acting as if I'm trying to log in using their method.

I read that backends are cached and so ran

Session.objects.all().delete()

to clear out the backends cache.

My main questions are:

  1. Does the order in which items are listed in AUTHENTICATION_BACKENDS
  2. How does the system decide/know which Backend to use? This was never made clear by any of the documentation, and I find it a bit confusing.
  3. Is there any way to force the use of a specific authorization based on the request. In other words, if someone submits a form, is there a way to force them to use the form-login-based authentication as opposed to the login via openid or Twitter?

Update:

It works! This is very cool, thanks. I guess it just seemed like the django doc was saying "You don't have to do anything else, it just sort of works like magic" and it turns out this is absolutely the case. So long as the backend is there and the credentials are set up correctly, the authentication will work. As it turns out the real problem was a misconfiguration in the urls.py file that wasn't sending the post from the login form to the correct handler, which is why it kept trying to use another authentication method.

+1  A: 
  1. It's clearly described here - django tries each backend in order defined, if first fails to authenticate it goes to second etc.

  2. I believe you can load backend class dynamically and authenticate directly through it. Look at django authenticate() function sources on how to do that.

Dmitry Shevchenko
I think the key was knowing that the credentials make a difference. The other thing that was entirely clear was if there was a way to format the authenticate function so it would target the backend I wanted.
Jordan Reiter
+1  A: 

You're supposed to use keyword arguments to django.contrib.auth.authenticate() The names should match the names of the arguments in your backend's authenticate method. The default backend handles the names 'username' & 'password'.

Your backend can use a different name for the keyword arguments e.g.: blahblah_email and blahblah_password, and then call authenticate(blahblah_email=..., blahblah_password=...).

lmz
+1  A: 

I guess django-cas will be a good reference for you :)

And yes, the order of AUTHENTICATION_BACKENDS matters.

Django loops over the backends list and stop at the first backend that has a authenticate method accepting the credential parameters you passed to it.

Satoru.Logic