I am trying to create an auth backend to allow my users to log in using either their email address or their username in Django 1.6 with a custom user model. The backend works when I log in with a user name but for some reason does not with an email. Is there something I am forgetting to do?
from django.conf import settings
from django.contrib.auth.models import User
class EmailOrUsernameModelBackend(object):
"""
This is a ModelBacked that allows authentication with either a username or an email address.
"""
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, username):
try:
return User.objects.get(pk=username)
except User.DoesNotExist:
return None
Edit: As suggested I have inherited from ModelBackend and installed it in my settings
In my settings I have this
AUTHENTICATION_BACKENDS = (
'users.backends',
'django.contrib.auth.backends.ModelBackend',
)
And I have changed the backend to this:
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
class EmailOrUsernameModelBackend(ModelBackend):
"""
This is a ModelBacked that allows authentication with either a username or an email address.
"""
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, username):
try:
return User.objects.get(pk=username)
except User.DoesNotExist:
return None
Now I get an Module "users" does not define a "backends" attribute/class error.
Yet another solution:
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class EmailOrUsernameModelBackend(ModelBackend):
"""
Authentication backend which allows users to authenticate using either their
username or email address
Source: https://stackoverflow.com/a/35836674/59984
"""
def authenticate(self, request, username=None, password=None, **kwargs):
# n.b. Django <2.1 does not pass the `request`
user_model = get_user_model()
if username is None:
username = kwargs.get(user_model.USERNAME_FIELD)
# The `username` field is allows to contain `#` characters so
# technically a given email address could be present in either field,
# possibly even for different users, so we'll query for all matching
# records and test each one.
users = user_model._default_manager.filter(
Q(**{user_model.USERNAME_FIELD: username}) | Q(email__iexact=username)
)
# Test whether any matched user has the provided password:
for user in users:
if user.check_password(password):
return user
if not users:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (see
# https://code.djangoproject.com/ticket/20760)
user_model().set_password(password)
Fixes:
By default, # is not prohibited in the username field, so unless custom User model prohibits # symbol, it can't be used to distinguish between username and email.
Technically, there can be two users using the same email, one in the email field, the other in the username. Unless such possibility is restricted, it can lead to either user not being able to authenticate, or unhandled MultipleObjectsReturned exception if UserModel._default_manager.get(Q(username__iexact=username) | Q(email__iexact=username)) is used.
Catching any exception with except: is generally bad practice
Downside - if there are two users, using the same email, one in the username, the other in email, and they have the same password, then it's prone to authenticating the first match. I guess the chances of this is highly unlikely.
Also note: any of the approaches should enforce unique email field in the User model, since the default User model does not define unique email, which would lead to either unhandled exception in case User.objects.get(email__iexact="...") is used, or authenticating the first match. In any case, using email to login assumes that email is unique.
After following the advice given to me above and changing AUTHENTICATION_BACKENDS = ['yourapp.yourfile.EmailOrUsernameModelBackend'] I was getting the error Manager isn't available; User has been swapped for 'users.User'. This was caused because I was using the default User model instead of my own custom one. Here is the working code.
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
class EmailOrUsernameModelBackend(ModelBackend):
"""
This is a ModelBacked that allows authentication
with either a username or an email address.
"""
def authenticate(self, username=None, password=None):
if '#' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
user = get_user_model().objects.get(**kwargs)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
def get_user(self, username):
try:
return get_user_model().objects.get(pk=username)
except get_user_model().DoesNotExist:
return None
I thought I'd chuck my simpler approach in for anyone else who comes across this:
# -*- coding: utf-8 -*-
from django.contrib.auth import backends, get_user_model
from django.db.models import Q
class ModelBackend(backends.ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
if user.check_password(password):
return user
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a non-existing user (#20760).
UserModel().set_password(password)
Note:
disregards USERNAME_FIELD, although you could add it back in pretty easily
case insensitive (you could just remove the __iexact's though to make it not)
I know this is already answered, however I have found a real neat way to implement login with both e-mail and username using the Django auth views. I did not see anyone use this type of method so I thought I'd share it for simplicity's sake.
from django.contrib.auth.models import User
class EmailAuthBackend():
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(raw_password=password):
return user
return None
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.py add this
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'myapp.authentication.EmailAuthBackend',
)
I wrote the code for this in 2 simple steps :
VIEWS.py
if request.method == 'POST':
userinput = request.POST['username']
try:
username = userbase.objects.get(email=userinput).username
except userbase.DoesNotExist:
username = request.POST['username']
password = request.POST['password']
INDEX.html
I created 2 input fields from which 1st is for username/email. I take whatever input is given and try to search the same data in email column of db, if it matches, I return the username and then try to authenticate , if it doesn't , I use input directly as Username.
I'm using Django 2.2
You can simply have a Try block to do this..
use:
email = request.POST['username']
raw_password = request.POST['password']
try:
account = authenticate(username=MyUserAccount.objects.get(email=email).username,password=raw_password)
if account is not None:
login(request, account)
return redirect('home')
except:
account = authenticate(username=email, password=raw_password)
if account is not None:
login(request, account)
return redirect('home')
Updated version of the same snippet, with improved security. Also, it allow you to enable or disable 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
if you are using django-rest-auth then the option to authenticate with email address is built in, and may conflict with the other methods proposed. You just need to add the following to settings.py:
ACCOUNT_AUTHENTICATION_METHOD = 'username_email'
ACCOUNT_EMAIL_REQUIRED = False
ACCOUNT_USERNAME_REQUIRED = False
#Following is added to enable registration with email instead of username
AUTHENTICATION_BACKENDS = (
# Needed to login by username in Django admin, regardless of `allauth`
"django.contrib.auth.backends.ModelBackend",
# `allauth` specific authentication methods, such as login by e-mail
"allauth.account.auth_backends.AuthenticationBackend",
)
Django rest auth email instead of username
https://django-allauth.readthedocs.io/en/latest/configuration.html
Note that unless you want to have a single box into which the user can type a username or an email address, you'll have to do some work in the front end to decide whether to send the login request as email, password or username, password. I did a simple test whether the user's entry contained an '#' with a '.' further on. I think somebody deliberately creating a username that looks like an email address - but isn't their email address - is unlikely enough that I'm not supporting it.
Those who are still struggling. Because of following multiple tutorials, we end up with this kind of a mess. Actually there are multiple ways to create login view in Django. I was kinda mixing these solutions in my Django predefined method of log in using
def login_request(request):
form = AuthenticationForm()
return render(request = request,
template_name = "main/login.html",
context={"form":form})
But now I have worked around this problem by using this simple approach without modifying the authentication backend.
In root directory (mysite) urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('django.contrib.auth.urls')),
]
In your app directory urls.py
urlpatterns = [
path('accounts/login/', views.login_view, name='login'),
]
In views.py
def login_view(request):
if request.method == 'POST':
userinput = request.POST['username']
try:
username = User.objects.get(email=userinput).username
except User.DoesNotExist:
username = request.POST['username']
password = request.POST['password']
user = auth.authenticate(username=username, password=password)
if user is not None:
auth.login(request, user)
messages.success(request,"Login successfull")
return redirect('home')
else:
messages.error(request,'Invalid credentials, Please check username/email or password. ')
return render(request, "registration/login.html")
Finally, In templates (registration/login.html)
<div class="container ">
<h2>SIGN IN</h2>
<form action="{% url 'login' %}" method="post">
{% csrf_token %}
<div class="form-group">
<label for="Password">Username or Email:</label>
<input type="text" class="form-control" placeholder="Enter User Name" name="username">
</div>
<div class="form-group">
<label for="Password">Password:</label>
<input type="password" class="form-control" placeholder="Enter Password" name="password">
</div>
<button type="submit" class="btn btn-dark">Sign In</button> <a class="" href="{% url 'password_reset' %}">
Forgot Password?
</a>
<p>Don't have an account? <a class="" href="{% url 'blog:signup' %}">
REGISTER NOW
</a></p>
</form>
</div>
This is the easiest solution I came up with.
I tried to go through the Django library to know how they authenticated the admin user, I've found some piece of code from the Django library authentication class the code which worked for me I'm attaching here.
Create a yourFavNameAuthenticate.py file in your app
and paste this code there
yourFavNameAuthenticate.py
from django.db.models import Q
from django.contrib.auth import get_user_model
UserModel = get_user_model()
class UsernameOrEmailBackend(object):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
if username is None or password is None:
return
try:
user = UserModel.objects.get(
Q(username=username) | Q(email=username))
except UserModel.DoesNotExist:
# Run the default password hasher once to reduce the timing
# difference between an existing and a nonexistent user (#20760).
UserModel().set_password(password)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def user_can_authenticate(self, user):
"""
Reject users with is_active=False. Custom user models that don't have
that attribute are allowed.
"""
is_active = getattr(user, 'is_active', None)
return is_active or is_active is None
def get_user(self, user_id):
try:
user = UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
Go to settings.py and paste a single line which will tell where to look for authentication files
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', #<- This is the Django's authentication class object
'appName.yourFavNameAuthenticate.UsernameOrEmailBackend', #<-This is yourFavNameAuthenticate.py class object
)
To keep things as simple as possible, you could just add a quick check to ModelBackend and alter the username passed if an email on record matches.
In this way, you minimize the effect you are having on the ModelBackend.
#myapp.auth.py
from django.contrib.auth.backends import ModelBackend, UserModel
class EmailThenUsernameModelBackend(ModelBackend):
"""
Overwrites functionaility for ModelBackend related to authenticate function
"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""
Check if passed username matches an email on record
If so: override the email with the username on record
Else: pass the username as is
"""
user=UserModel._default_manager.filter(email=username).first()
if user:
username=user.username
return super().authenticate(request, username, password, **kwargs)
# myapp.settings.py
...
AUTHENTICATION_BACKENDS = ['myapp.auth.EmailThenUsernameModelBackend']
By default django does not force emails to be unique. You could simply add this to a custom user model:
# myapp.models.py
class CustomUser(AbstractUser):
# Overwrite email field at django.contrib.auth.models.AbstractUser
# Force email field to exist and to be unique
email = models.EmailField(
_('email address'),
unique=True
)
Note: This does not include the extra steps needed to get the custom user setup.
Here's a work-around that doesn't require modifying the authentication backend at all.
First, look at the example login view from Django.
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...
If authentication with the username fails we can check if there is an email match, get the corresponding username, and try to authenticate again.
from django.contrib.auth import authenticate, login, get_user_model
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is None:
User = get_user_model()
user_queryset = User.objects.all().filter(email__iexact=username)
if user_queryset:
username = user_queryset[0].username
user = authenticate(username=username, password=password)
if user is not None:
login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...
Similar to 1bit0fMe's example, email should be a unique field and there is the same (highly unlikely) downside that they mentioned.
I would only recommend this approach if all login on your site is handled by a single view or form. Otherwise, it would be better to modify the authenticate() method itself in the backend to avoid creating multiple points of potential failure.
Assuming you have blocked/forbidden against the username having an #, and you want to use the django User model.
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
cd=form.cleaned_data
if '#' in cd['username']:
username=User.objects.get(email=cd['username']).username
else:
username=cd['username']
user = authenticate(username=username,
password=cd['password'])
if user is not None and user.is_active:
login(request,user)
return redirect('loggedin')
else:
return render(request, 'login.html')
Note that for the most solutions like these you should add email uniqueness validation for User model to avoid the authentication vulnerability
# models.py
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
objects = UserManager()
email = models.EmailField(_('email address'), unique=True)
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
db_table = 'auth_user'
swappable = 'AUTH_USER_MODEL'
and then you have to update settings.py defining the AUTH_USER_MODEL property
AUTH_USER_MODEL = '[your_app_name].User'
I implemented the solution in my view. However, my users are not allowed to have emails as username during registration and also each email is unique.
if request.method=="POST":
username = request.POST.get('username').lower()
password = request.POST.get('password')
'''check if the username is a valid email'''
try:
email = validate_email(username)
username = User.objects.get(email=username).username
except:
pass
user = authenticate(request, username=username, password=password)
if user is not None:
login(request,user)
else:
messages.error(request,("Error logging in."))
return redirect('login')
I am using validate_email so that my users can have # in their usernames, #bestuser is a valid username but not a valid email. It works for me and I don't have to overwrite the authenticate method.
very simple solution without overriding backends authenticate
email_or_username = request.data.get('email_or_username', None).lower()
password = request.data.get('password', None)
auth_username = email_or_username if ('#' in email_or_username) else User.objects.get(username__iexact=email_or_username).email
user = authenticate(username=auth_username, password=password)
Related
I'm a newbie in django and I'm having some problems, what I'm trying to do is a simple login system in Django with a custom backend and using postgresql as the main db.
The problem is, my authentication and login function is apparently working normally but the user is not actually logged in, I wrote a custom message to let me know when the user is logged in and my index is protected against anonymous user, so I basically can't access.
This code from my views.py
#login_required(login_url='signin')
def index(request):
return render(request, 'index.html')
def signin(request):
now = datetime.now()
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
print(username, password)
user = UserBackend.authenticate(UserBackend(), username=username, password=password)
if user is not None:
login(request, user, backend='core.backends.UserBackend')
print('Logged In') #### This print/checking is working fine!
return redirect('/')
#return render(request, 'index.html')
else:
messages.info(request, 'Invalid Username or Password! Try again')
return redirect("signin")
#else:
return render(request,'signin.html')
#user = auth.authenticate(username=username, password=password)
return render(request, 'signin.html')
This is my user class from models.py
class user(AbstractBaseUser):
id = models.AutoField(primary_key=True)
username = models.TextField()
real_name = models.TextField()
email = models.TextField()
password = models.TextField()
description = models.TextField()
last_login = models.DateField()
created_at = models.DateField()
is_staff = models.BooleanField(default=False)
is_active = models.BooleanField(default=True)
class Meta:
managed = False
db_table = 'user'
def __str__(self) -> str:
return self.username
And my user backend from backends.py
class UserBackend(ModelBackend):
def authenticate(self, **kwargs):
username = kwargs['username']
password = kwargs['password']
print('user: ', username)
print('pass: ', password)
#try:
user_ = user.objects.get(username=username)
try:
print(user_.check_password(password))
if user_.check_password(password) is True:
return user_
except user.DoesNotExist:
pass
def get_user(self, user_id):
try:
return user.objects.get(pk=user_id)
except user.DoesNotExist:
return None
I tried changing the return on views.py to something like
return redirect(request.GET.get('next'))
but still not working :(
what should I do?
The key point, is how you are using your Backend. Although, lets start from the beginning...
Starting with your models, you are unnecessarily overwriting a lot of fields. Django's AbstractUser has these fields you are creating. In fact, the model contains the following fields with validators where appropriate:
username
first_name
last_name
email
is_staff as default false
is_active as default true
date_joined as default timezone.now()
last_login
a built-in function get_full_name (equivalent to your real_name field)
Also, id = models.AutoField(primary_key=True) is not necessary Django by default creates this kind of field automatically for every model. Most of the times used when you want a different kind of field as PK. Thus a simple model, would fulfill your requirements:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
description = models.TextField()
The backend is pretty straight forward, check credentials and return an User object instance if everything is correct. ModelBackend already has a get_user method defined.
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.hashers import check_password
from django.contrib.auth import get_user_model
User = get_user_model()
class UserBackend(ModelBackend):
def authenticate(self, request, **kwargs):
username = kwargs['username']
password = kwargs['password']
try:
user = User.objects.get(username=username)
pwd_valid = check_password(password, user.password)
if pwd_valid:
return user
else:
return None
except User.DoesNotExist:
return None
Now, about on how to use it. Quotting the documentation:
Behind the scenes, Django maintains a list of “authentication
backends” that it checks for authentication. When somebody calls
django.contrib.auth.authenticate() Django tries authenticating across
all of its authentication backends. If the first authentication method
fails, Django tries the second one, and so on, until all backends have
been attempted.
The list of authentication backends to use is specified in the
AUTHENTICATION_BACKENDS setting. This should be a list of Python path
names that point to Python classes that know how to authenticate.
These classes can be anywhere on your Python path.
So, first we need to add AUTHENTICATION_BACKENDS in settings.py:
AUTH_USER_MODEL = 'core.User'
LOGIN_URL = '/signin'
AUTHENTICATION_BACKENDS = [
'core.auth.backends.UserBackend',
'django.contrib.auth.backends.ModelBackend'
]
And lastly in views.py, we call the functions exactly as you would in a normal login function:
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from django.contrib.auth import login, authenticate
from django.contrib import messages
# Create your views here.
#login_required
def index(request):
return render(request, 'index.html')
def signin(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# Confirm that backend is UserBackend
print(user.backend)
return redirect('core:index')
else:
messages.info(request, 'Invalid Username or Password! Try again')
return redirect("core:signin")
return render(request, 'signin.html')
I write my own authentication backend in file project/emailauth.py with content:
from django.contrib.auth.hashers import check_password
from django.contrib.auth.models import User
class EmailBackend(object):
def authenticate(self, username=None, password=None):
print("Email auth")
try:
user = User._default_manager.get(email=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
except:
return None
def get_user(self, user_id):
try:
return User._default_manager.get(pk=user_id)
except User.DoesNotExist:
return None
In my project/settings.py at the end of the file I added the content:
AUTHENTICATION_BACKENDS = (
'project.emailauth.EmailBackend',
'django.contrib.auth.backends.ModelBackend',
)
I use authenticate methon in userprofile/views.py:
from django.contrib.auth import authenticate, login, logout
#...
if form.is_valid():
username = form.cleaned_data['username']
password = form.cleaned_data['password']
profile = authenticate(username=username, password=password)
if profile is not None:
login(request, profile)
return HttpResponseRedirect(redirect_to)
#...
The most interesting I notice that console output Email auth only when I try to login by username and password if I use email and password there is not output in console. Looks like my custom authenticate fire up after standard method...
Can someone show me where I made a mistake?
It turned out my forms validation block hole login by email (becouse it's check if users exist in database before it try to authenticate)
While using Django user is it possible somehow to use multiple login fields?
Like in Facebook where we can login using username, email as well as the phone number.
Yes, it is! You can write your own authentication backend, as this section describes:
https://docs.djangoproject.com/en/1.8/topics/auth/customizing/
We suppose you have created an application account/ where you extend the User model for the additional phone field.
Create your auth backend backends.py inside account/ and write you own authentication logic. For example:
from account.models import UserProfile
from django.db.models import Q
class AuthBackend(object):
supports_object_permissions = True
supports_anonymous_user = False
supports_inactive_user = False
def get_user(self, user_id):
try:
return UserProfile.objects.get(pk=user_id)
except UserProfile.DoesNotExist:
return None
def authenticate(self, username, password):
try:
user = UserProfile.objects.get(
Q(username=username) | Q(email=username) | Q(phone=username)
)
except UserProfile.DoesNotExist:
return None
return user if user.check_password(password) else None
Put you custom backend in you project's settings.py:
AUTHENTICATION_BACKENDS = ('account.backends.AuthBackend',)
Then just authenticate by passing username, email or phone in the POST's username field.
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
# Redirect to a success page.
else:
# Return a 'disabled account' error message
...
else:
# Return an 'invalid login' error message.
...
see: https://docs.djangoproject.com/fr/1.8/topics/auth/default/
I just completed the Django tutorial and want to create a website that requires users to sign in with an email address and password. This is completely separate from Django's awesome built-in Admin site (only one or two special people should have access to the Admin).
My questions are:
Should I create a separate database table to store user credentials, info, etc? Or should I use an existing Django table?
As mentioned, users should sign in with an email address. Will this be a problem for Django?
Other best-practices would be much appreciated. Thank you.
You won't need a separate table for user logins. Just leverage the Auth framework Allowing users to sign in with their email address is pretty simple, but there are a few different ways to approach the subject. You'll want to start with: https://docs.djangoproject.com/en/1.7/topics/auth/customizing/
First what is middleware
It’s a light, low-level “plugin” system for globally altering Django’s input or output.
And you can read here how to activating-middleware
Creating email backend middleware will help you to achieve authenticate a user based on email address as the user name
def authenticate(self, username=None, password=None):
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
Brandon is right,
The simplest way to do this is to customize the backend instead of the Django default
Let's do this
In your settings.py:
AUTHENTICATION_BACKENDS = ('accounts.backend.EmailOrUsernameModelBackend')
Then in the accounts/backend.py:
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend as _ModelBackend
User = get_user_model()
class EmailOrUsernameModelBackend(_ModelBackend):
""" Authenticate user by username or email """
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
else:
return None
except User.DoesNotExist:
return None
def get_user(self, user_id=None):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Now you can login with your username or email
Have fun
add this to settings.py
AUTHENTICATION_BACKENDS = ['accounts.backends.EmailOrUsernameAuthenticationBackend']
and add this to accounts/backends.py
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth.models import User
class EmailOrUsernameAuthenticationBackend(BaseBackend):
def authenticate(self, request, **kwargs):
username = kwargs['username']
password = kwargs['password']
try:
if '#' in username:
my_user = User.objects.get(email=username)
else:
my_user = User.objects.get(username=username)
except User.DoesNotExist:
return None
else:
if my_user.is_active and my_user.check_password(password):
return my_user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I'm using the UserModel and the UserCreationForm in Django. However, instead of requiring a username, I want to use the customer's email as the login information (i.e. completely bypass / ignore the username field).
How do I go about making the username field to be optional in the User Model
How do I turn email into the customer's "username"? In turn, how do I make this email field to be required then?
1.) Just make the username field optional in your form and to the model pass the email as the username, after stripping out '#' from the email.
2) Create a custom authentication backend (this will accept either the username or email):
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
chage your settings accordingly :
AUTHENTICATION_BACKENDS = (
'myoursite.backends.EmailOrUsernameBackend', # Custom Authentication to accept usernamee or email
)