23

Aug

Filed in Code, Django |

One of the tasks that seems to come up every single project we do, is changing the auth backend to accept email addresses. Since it’s such a common task for us, it can’t be that rare to want this functionality. So, here’s a quick and simply backend which accomplishes this, using the built-in django.contrib.auth module.

from django.conf import settings
from django.contrib.auth.models import User
 
class EmailOrUsernameModelBackend(object):
    def authenticate(self, username=None, password=None):
        if '@' in username:
            kwargs = {'email': username}
        else:
            kwargs = {'username': username}
        try:
            user = User.objects.get(**kwargs)
            if user.check_password(password):
                return user
        except User.DoesNotExist:
            return None
 
    def get_user(self, user_id):
        try:
            return User.objects.get(pk=user_id)
        except User.DoesNotExist:
            return None

Then in your settings:

AUTHENTICATION_BACKENDS = (
    'myproject.accounts.backends.EmailOrUsernameModelBackend',
    'django.contrib.auth.backends.ModelBackend'
)

13 Responses to "Logging In With Email Addresses in Django"

Subscribe to this topic with RSS or get the Trackback URL
sean (Aug 23rd):

wow, it’s the piece of code i’m trying to get.
thanks for posting it, again!

gnrfan (Aug 23rd):

Yes, Django is flexible enought to permit this just by adding this small piece of code.

One quirk arises when logging in with an email address in the admin site: if the authentication does not success for any reason you’ll get a “Usernames cannot contain the ‘@’ character.” error message which is misleading because Django is having no problem to log you in using an email address at this point.

So to correct that you should override the login() method of the admin object as pointed out by James Bennet in this bug:

http://code.djangoproject.com/ticket/8342

Antonio,
Lima – Peru

gnrfan (Aug 23rd):

I just tried the code and realized the backend is missing the other methods that ModelBackend implements so I made EmailOrUsernameModelBackend inherint from ModelBackend instead of object and now everything works like a charm.

The code is here at PasteThat:

http://www.pastethat.com/django_email_login

Regards,

Antonio

David (Aug 25th):

Ya I did notice that get_user is still required. I updated the above code.

The reason it doesn’t inherit from ModelBackend is that this allows you to specify a second backend to use for permissions.

julien (Aug 25th):

Hey, just a quick note. If using the standard login views, you won’t be able to login with an email that’s longer than 30 characters. There’s a ticket with a simple patch which hopefully will get checked in to fix that:
http://code.djangoproject.com/ticket/8274

Cheers,

Julien Phalip

carljm (Sep 18th):

> The reason it doesn’t inherit from ModelBackend is that this allows you to specify a second backend to use for permissions.

Could you expand on this? I’ve been using an EmailBackend similar to yours, except that it inherits from ModelBackend, overrides only the authenticate() method, and I use it all by itself in AUTHENTICATION_BACKENDS. This seems to work just fine. What am I missing that your approach provides?

markhellewell (Apr 28th):

Nice, thanks. It pleases me to find out Django allows such elegance

keredson (Jun 9th):

how do you force emails to be unique in django’s default admin? i dropped this code into my test app, and the first thing it gave me was “warning, returned 3 objects not 1!” during “User.objects.get(**kwargs)”.

zalun (Jun 30th):

@keredson
I use similar approach.
I just set the username as a slug of email replacing every weird character with _ and @ with __
Seems to work fine

@David
The only problem I have for the moment is the Admin interface to create users instead of allowing them to register…

peterp (Jul 14th):

A user can register the same email address more than once. So I’ve modified the code a bit to filter by email addresses.

It then loops through each user found and checks the password individually.

steg (Oct 10th):

A possibly naive question:

The EmailOrUsernameModelBackend allows a user to use their email or username during authentication.
This backend is then added to the list of AUTHENTICATION_BACKENDS **before** the standard ModelBackend, which performs authentication using the username User field.

My question is why does the EmailOrUsernameModelBackend need to support both email and username, rather than just email? To illustrate my point, if I was to log in with username “steg”, the EmailOrUsernameModelBackend.authenticate() method would try to retrieve a User object from the db using keyword arguments {“username”:”steg”}. If this fails, django then moves on and tries the ModelBackend.authenticate() method, which will do exactly the same thing.

Are there other reasons for this apparent duplication? I’m pretty new to django, so apologies in advance if I’m missing something obvious.

cornbread (Nov 12th):

I have the same question as steg.

also this seems backward to me. Why didn’t django think “hmm maybe someone might want email as username”

it seems like an ugly hack to attempt to make email unique and can’t use built in login form. Is this so hard to fix?

p.s. Please explain the permissions comment…

tyagi (Dec 19th):

Have the Django developers given a reason for not wishing to enable Email logins for the built-in User authentication model? Alternatively, have they given a reason for not permitting non-alphanumeric characters in the username field?

Searching for solutions to this issue seems to give a large number of hits for fragmented and 90% working patches or backend extensions. Given that users of Django are obviously experiencing some problems with this developer policy, and the need for Email Usernames is quite apparent, is there a particular reason why they’re not willing to implement it?

Leave A Reply

 Username (*required)

 Email Address (*private)

 Website (*optional)

Note: Comments moderation may be active so there is no need to resubmit your comment.