Using email instead of django login

Firstly, it is not a question of how to authenticate by email / password, but rather how to create a logical and, if you like, beautiful data structure.

I want to use emails as usernames in this django project. However, I cannot reuse the fields provided by the auth.User model for at least two reasons:

  • The auth.User.username max_length field is 30 characters, which may not be enough for some email addresses.

  • auth.User.email is not unique - which is clearly not satisfactory for the premise that usernames must be unique.

Thus, the obvious way is to save the username in a custom profile that is associated with auth.User. In this case, we are dealing with the following problems:

  • Create a unique username for auth.User.username - should the md5 email hash be okay?
  • Leave auth.User.email empty completely - since it contains only 75 characters, and in accordance with RFC 5321 ( What is the maximum length of a valid email address? ) It can be up to 256 characters long.

The following problems arise from the proposed solution:

  • One of them will not be able to reuse the built-in views / templates for standard operations such as password reset, etc.
  • If you change the email, auth.User.username will need to be updated.

To add oil to the fire, django developers are unlikely to fix this limitation in the foreseeable future - see http://code.djangoproject.com/ticket/11365 p>

So the question is: is there another way to do this, and you see any other flaws in the solution proposed above?

Thanks!

+7
database django data-structures architecture
source share
6 answers

I had a client with a commercial site that has been since 1995 (yes, we are talking about early adopters here). In any case, they already had an established user base, and the names were completely incompatible with Django's idea of ​​a username.

I looked at several ways to handle this, and they all felt hacked (this was the summer of 2007), so I said I twisted it and hacked contrib.auth.models.User directly. I needed to change only 10 lines of code, increase the size of the field and configure the validator. Since then, we have made two updates - 0.97-pre => 1.0 and 1.0 => 1.1.1 - and it only took about 15 minutes each time to “transfer the hack”.

It's ugly and I can burn in hell for this, but it took less time than anything I could figure out, and the advanced ports were completely without problems.

+5
source share

I wrote an explanation of my solution to the same problem: Django Authentication using an email address . It consists mainly of:

  • Create your own authorization credential for email authentication.
  • A subclass of the user creation form for adding an email address as a required field.
  • Hide the username field from the user in the creation and registration forms.
  • Arbitrary creation of a username in a view that processes the creation form.
  • Manually add a unique index to the email column (Yuck!)

My solution still has 2 problems. Firstly, manually creating a database index is not very good. Secondly, the letter is still limited to 75 characters (I had no problems porting the system to about 8,000 users). But it plays well with the rest of Django and third-party applications.

+1
source share

I must also admit that I’ll burn in hell. I recently deployed a small application in which I deleted a user’s email, cut it up to 30 characters and set it as a username. I thought, damn it, what are the odds? and went through it. Took a few lines of code and voila.

I think the 75 character limit was set as such, because people usually do not have personal letters that take a long time. It is simply a matter of saving space, because all unused bytes will always be reserved (i.e. NULL and shorter / smaller values ​​are not free).

0
source share

I just use this djangosnippet and users can use either their username or their email.
But this will not solve the 75 character limit, just a convenient snippet.

0
source share

I wrote a solution based on Dominique's answer that includes security enhancements and some additional features like case-sensitive authentication. If you prefer, you can install it directly from pypi :

from django.contrib.auth.backends import ModelBackend from django.contrib.auth import get_user_model from django.conf import settings ################################### """ DEFAULT SETTINGS + ALIAS """ ################################### try: am = settings.AUTHENTICATION_METHOD except: am = 'both' try: cs = settings.AUTHENTICATION_CASE_SENSITIVE except: cs = 'both' ##################### """ EXCEPTIONS """ ##################### VALID_AM = ['username', 'email', 'both'] VALID_CS = ['username', 'email', 'both', 'none'] if (am not in VALID_AM): raise Exception("Invalid value for AUTHENTICATION_METHOD in project " "settings. Use 'username','email', or 'both'.") if (cs not in VALID_CS): raise Exception("Invalid value for AUTHENTICATION_CASE_SENSITIVE in project " "settings. Use 'username','email', 'both' or 'none'.") ############################ """ OVERRIDDEN METHODS """ ############################ class DualAuthentication(ModelBackend): """ This is a ModelBacked that allows authentication with either a username or an email address. """ def authenticate(self, username=None, password=None): UserModel = get_user_model() try: if ((am == 'email') or (am == 'both')): if ((cs == 'email') or cs == 'both'): kwargs = {'email': username} else: kwargs = {'email__iexact': username} user = UserModel.objects.get(**kwargs) else: raise except: if ((am == 'username') or (am == 'both')): if ((cs == 'username') or cs == 'both'): kwargs = {'username': username} else: kwargs = {'username__iexact': username} user = UserModel.objects.get(**kwargs) finally: try: if user.check_password(password): return user except: # Run the default password hasher once to reduce the timing # difference between an existing and a non-existing user. UserModel().set_password(password) return None def get_user(self, username): UserModel = get_user_model() try: return UserModel.objects.get(pk=username) except UserModel.DoesNotExist: return None 
0
source share

All Articles