Authenticate users with both username and email

705 views
Skip to first unread message

Kakar Nyori

unread,
Jan 15, 2015, 6:07:29 AM1/15/15
to django...@googlegroups.com

I have extendted the UserCreationForm with email and other fields, so that I could authenticate a user with both its username and email.

forms.py:

class UserCreationForm(UserCreationForm):
class Meta:
model = User
fields = ('first_name', 'last_name', 'username', 'email',)

 
views.py:

def auth_view(request):
    username = request.POST.get('username','')
    password = request.POST.get('password','')
    user = auth.authenticate(username=username, password=password)
    if user is not None:
        auth.login(request, user)
        return HttpResponseRedirect('/')
    elif:
        user = auth.authenticate(email=username, password=password)
        if user is not None:
            auth.login(request, user)
            return HttpResponseRedirect('/')
    else:
        return HttpResponseRedirect('/accounts/invalid_login')

html:

<form action="/accounts/auth/" method="post">
    {%csrf_token%}
    <label for="name">Email or Username:</label>
    <input type="text" name="name" id="name" value="">
    <label for="password">Password:</label>
    <input type="password" name="password" id="password" value="">
    <input type="submit" value="LOGIN">
</form>


In the views I tried giving both the username and email as input from the form as name, and check to see if username and password authenticate. If not then check whether email and password authenticate. But its not working. How do I solve this problem? Please kindly help me. Thank you.

Matt Cooper

unread,
Jan 15, 2015, 9:07:13 PM1/15/15
to django...@googlegroups.com
Your if block in views.py is not well-formed. I haven't tested this but I'd write it more like this:

    # try username
    user = auth.authenticate(username=username, password=password)
    if user is not None:
        auth.login(request, user)
        return HttpResponseRedirect('/')
    # fall-through to email
    user = auth.authenticate(email=username, password=password)
    if user is not None:
        auth.login(request, user)
        return HttpResponseRedirect('/')
    # ok, neither one worked
    return HttpResponseRedirect('/accounts/invalid_login')

Abraham Varricatt

unread,
Jan 19, 2015, 9:26:59 AM1/19/15
to django...@googlegroups.com
Ignoring the malformed code, will the call to authenticate() even work without username? According to the docs,

It takes credentials in the form of keyword arguments, for the default configuration this is username and password, and it returns a User object if the password is valid for the given username.


If someone wanted to change the default behavior, I would expect them to at least override the function. OP doesn't seem to have done this, so it shouldn't even work. (Please correct me if I'm missing something)

-Abraham V.

James Schneider

unread,
Jan 19, 2015, 10:33:35 AM1/19/15
to django...@googlegroups.com
I guess I'm not clear on what you are trying to achieve. There are a couple of scenarios to consider.

As it stands with the default contrib.auth authentication backend, sending both the username and email address entered by the user will only work IF the user registered/was created using their email address as their username, meaning that user.username is either their username (a string that isn't their email), or an email address, regardless of what user.email returns. The default contrib.auth authentication backend only looks at user.username.

What I suspect you want is to be able to use either user.username OR user.email as the identifying tokens for the user in order to authenticate them. For that, you'll need to roll at least a partial custom authentication backend, in addition to the changes for the forms, etc. that you've already laid out. 


"If you wish to provide custom behavior for only part of the backend API, you can take advantage of Python inheritance and subclass ModelBackend instead of implementing the complete API in a custom backend."

Specifically, you'll need to override the authenticate() method of the authentication backend.

I've done this, it's actually pretty easy. See:


In this case, you should also roll a custom user model as well to ensure that both user.username and user.email are unique columns. If you use the email address as an identifying token, it will need to be unique among all users, which is not true/enforced for the built-in contrib.auth User model. You will also need to override the custom User manager for your new CustomUser, probably with and override for get_by_natural_key(token) so that the user can be retrieved in the backend authenticate() method. The key change in get_by_natural_key() being the filter for the user object being changed to something like User.objects.get(Q(username=token) | Q(email=token)).

(If you haven't seen the Q() notation, check https://docs.djangoproject.com/en/1.7/ref/models/queries/#q-objects)

The custom user you probably want is somewhat similar to the example given in the docs:


You'll need to add a 'username' field back in (remember to make it unique) along with several other tweaks to be able to use either a username or email field. An alternative would be a custom user class inheriting from AbstractBaseUser and copying in the goodies you need from AbstractUser (which is probably everything except the username and email fields, which you'll be defining as unique). Be sure to include the Permission mixins that AbstractUser has if you intend to use the built-in permission system with your CustomUser.

Sorry about the lengthy message, there are a bunch of ways to tackle this, but ultimately I think you'll be rolling your own user and at least part of the authentication system, not to mention having to customize the user forms. The user will be slightly tricky, as there is no clear inheritance path that will make it easy AFAICT, but the auth backend should be just a class with a single authenticate() function if you inherit from ModelBackend. Be sure to either include your custom backend in AUTHENTICATION_BACKENDS, or possibly even replacing it if you go with the Q() query I specified earlier (since an auth request using an email would generate two authentication queries if both backends are used, and the username field is checked twice).

TL;DR; Overriding the user forms is probably not enough, you'll need a custom user and custom authentication backend.

Not sure if I made the situation better or worse, but HTH...

Obviously, YMMV, I haven't tried this myself, but I have done a fair bit of overriding for a custom object-based permission system, which touches many of the same structures above.

-James






--
You received this message because you are subscribed to the Google Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/c7e27779-311b-43f4-b66d-d3548becdc26%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Abraham Varricatt

unread,
Jan 20, 2015, 6:33:22 AM1/20/15
to django...@googlegroups.com
Damn. That's one heck of an explanation James! And very detailed to boot!

As a programmer, the OP's approach to login a user with either username/email with ONLY the default auth system seemed flawed (or incomplete, at best) to me. You've not just validated my opinion, but given me a wonder explanation to dig into. Thanks a lot! :)

Learning more django,
Abraham V.

Patrick Breitenbach

unread,
Nov 4, 2015, 8:36:57 AM11/4/15
to Django users
Are you suggesting that there's a relatively easy way to implement "email address as username"? Is it as easy as just putting an email address into the username field? And making sure to update it when someone updates their email address?

Russell Keith-Magee

unread,
Nov 4, 2015, 1:47:11 PM11/4/15
to Django Users

Yes, there *is* a very simple way to implement “Email address as username” -  you use a custom user model that implements that approach.


Django’s docs contain details of how to do this, and there are a number of third party projects that have a ready-to-use implementation of “email address as username” specifically.

Yours,
Russ Magee %-)

Reply all
Reply to author
Forward
0 new messages