I am new to django and I wanted to authenticate user on email or username with password hence I wrote a custom authentication as shown in documentation but it doesn't seem to be called and I have no idea what do I do?
settings.py
AUTHENTICATION_BACKENDS = ('accounts.backend.AuthBackend',)
views.py
def login(request):
if request.method == 'POST':
username_or_email = request.POST['username']
password = request.POST['password']
user = authenticate(username=username_or_email, password=password)
print(user)
if user is not None:
return reverse('task:home')
else:
messages.error(request, "Username or password is invalid")
return render(request, 'accounts/login.html')
else:
return render(request, 'accounts/login.html')
backend.py
from django.contrib.auth.models import User
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 User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
def authenticate(self, username, password):
print('inside custom auth')
try:
user = User.objects.get(
Q(username=username) | Q(email=username) )
print(user)
except User.DoesNotExist:
return None
print(user)
if user.check_password(password):
return user
else:
return None
I wrote this print statements in my class to check if they are being called and being written in console. However, they are not being printed and the print statement in views.py prints None
You need to extend the ModelBackend from django.contrib.auth.backends
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
User = get_user_model()
class AuthBackend(ModelBackend):
supports_object_permissions = True
supports_anonymous_user = False
supports_inactive_user = False
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
def authenticate(self, request, username=None, password=None):
print('inside custom auth')
try:
user = User.objects.get(
Q(username=username) | Q(email=username) )
print(user)
except User.DoesNotExist:
return None
print(user)
if user.check_password(password):
return user
else:
return None
And also in settings.py don't forget to add your custom backend authentication
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'accounts.backend.AuthBackend'
]
Another Possible Solution
From you code what I am seeing is that you want your email should treat as user_name of User model. You can easily modify Django's AbstructUser model like following
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
# your necessary additional fields
USERNAME_FIELD = 'email' # add this line
Now email field will treat as an user_name field. No need to add custom authentication-backend
import sys
from django.contrib.auth import get_user_model
from accounts.models import Token
User = get_user_model()
class PasswordlessAuthenticationBackend:
def authenticate(self, uid=None):
print('uuuuuuuuu')
print('uid', uid, file=sys.stderr)
if not Token.objects.filter(uid=uid).exists():
print('no token found', file=sys.stderr)
return None
token = Token.objects.get(uid=uid)
print('got token', file=sys.stderr)
try:
user = User.objects.get(email=token.email)
print('got user', file=sys.stderr)
return user
except User.DoesNotExist:
print('new user', file=sys.stderr)
return User.objects.create(email=token.email)
def get_user(self, email):
try:
return User.objects.get(email=email)
except User.DoesNotExist:
return None
Related
I've made a user model with USERNAME_FIELD defined as phone_number. So login form requires phone_number and password. I want users to be able to also login through their emails. So I created an authentication backend class. Users can login with their phone numbers but they canbot do so with their emails and will receive the 'Username and/or password is wrong' message.
authentication.py:
from django.contrib.auth import get_user_model
class CustomAuthBackend:
def authenticate(self, username=None, password=None):
try:
user = get_user_model().objects.get(email=username)
if password:
if user.check_password(password):
return user
return None
except:
return None
def get_user(self, user_id):
try:
user = get_user_model().objects.get(pk=user_id)
return user
except:
return None
forms.py:
class UserLoginForm(forms.Form):
username = forms.CharField(label="Phone Number / Email")
password = forms.CharField(widget=forms.PasswordInput(), label="Password")
views.py:
class UserLogin(View):
form_class = UserLoginForm
template_name = "accounts/login.html"
def get(self, request):
return render(request, self.template_name, {"form": self.form_class})
def post(self, request):
form = self.form_class(request.POST)
if form.is_valid():
cd = form.cleaned_data
user = authenticate(
request, username=cd["username"], password=cd["password"]
)
if user:
login(request, user)
messages.success(request, "Logged in successfully.", "success")
return redirect("home:home")
else:
messages.error(request, "Username and/or password is wrong.", "danger")
return render(request, self.template_name, {"form": form})
messages.error(request, "Login failed", "danger")
return render(request, self.template_name, {"form": form})
settings.py:
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"accounts.authentication.CustomAuthBackend",
]
Assuming that you have already included the custom backend in AUTHENTICATION_BACKENDS setting in settings.py file.
You can make a condition check that whether it is a phone no. or email using regex so:
import re
from django.contrib.auth import get_user_model
class CustomAuthBackend:
def authenticate(self, request, username=None, password=None):
UserModel = get_user_model()
# Check whether username is an email address or phone number
if re.match(r'^\+?\d{10,14}$', username):
try:
user = UserModel.objects.get(phone_number=username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
return None
else:
try:
user = UserModel.objects.get(email=username)
if user.check_password(password):
return user
except UserModel.DoesNotExist:
return None
def get_user(self, user_id):
try:
return get_user_model().objects.get(pk=user_id)
except get_user_model().DoesNotExist:
return None
I had forgotten to include request as a parameter in authenticate method. :)
Correct version:
def authenticate(self, request, username=None, password=None):
# ...
I want to login with email not username like this, please help
class loginUser(View):
def get(self, request):
lF = loginForm
return render(request, 'UserMember/login.html', {'lF': lF})
def post(self, request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return render(request, 'UserMember/private.html')
else:
return HttpResponse('login fail')
First thing is to create a default email field.
# models.py
class CustomUser(AbstractUser):
email = models.EmailField(_('email address'), unique=True)
# settings.py (remember to migrate)
AUTH_USER_MODEL = 'accounts.CustomUser' # new
Next, create your custom email backend:
# backends.py (in-app)
class EmailBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
user = UserModel.objects.get(
Q(username__iexact=username) | Q(email__iexact=username))
except UserModel.DoesNotExist:
UserModel().set_password(password)
except MultipleObjectsReturned:
return User.objects.filter(email=username).order_by('id').first()
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
def get_user(self, user_id):
try:
user = UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
return user if self.user_can_authenticate(user) else None
# settings.py (migrate again)
AUTH_USER_MODEL = 'accounts.CustomUser'
AUTHENTICATION_BACKENDS = ['accounts.backends.EmailBackend'] # new
If you plan on using Django's default register/login forms, do:
# form.py
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth import get_user_model
from django import forms
class RegisterForm(UserCreationForm):
class Meta:
model = get_user_model()
fields = ('email', 'username', 'password1', 'password2')
class LoginForm(AuthenticationForm):
username = forms.CharField(label='Email / Username')
And then it's only the views and URLs to handle.
Ref
you need to work with allAuth
Follow this Link
hope it will be helpful.
the files below is about custom authentication, mainly to use email as username and it works fine on Django 2.0.x. I'm starting a fresh new project in Django 2.2 and it doesn't work anymore. Why?
authmanager/helper_functions.py
from django.contrib.auth import authenticate, login, logout
def _login(request):
if request.method == 'POST':
username = request.POST['email']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return True
else:
return False
else:
return False
def _logout(request):
request.session.flush()
logout(request)
return True
authmanager/mybackend.py
from django.contrib.auth.hashers import check_password
from django.contrib.auth import backends
from .models import User
class MyCustomBackend(backends.ModelBackend):
def authenticate(self, username=None, password=None, **kwargs):
try:
user = User.objects.get(email=username)
if user and check_password(password, user.password):
return user
else:
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
settings.py
AUTHENTICATION_BACKENDS = ['crm.authmanager.mybackend.MyCustomBackend',]
AUTH_USER_MODEL = 'authmanager.User'
In a django project i'm working on I wrote a function to validate the data from a login form. Now whenever I run the is_valid() method on a instance of the login form it returns false everytime. I've been trying to figure out what's causing this problem all day.
forms.py:
from django import forms
from django.contrib.auth import authenticate, password_validation
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User, Group
from .functions import *
from .models import *
class login_form(forms.Form):
username = forms.CharField(max_length=64, label='Username', widget=forms.TextInput(attrs={'placeholder': 'username...', 'class':'login-field'}))
password = forms.CharField(max_length=24, label='Password', widget=forms.PasswordInput(attrs={'placeholder': 'password...', 'class':'login-field'}))
def clean(self, *args, **kwargs):
username = self.cleaned_data.get('username', None)
password = self.cleaned_data.get('password', None)
user_obj = None
self._errors['username'] = None #clears default username error
self._errors['password'] = None #clears default password error
if username is not None:
try:
user_obj = User.objects.get(username=username)
if password_validation.validate_password(password, user_obj) is None:
self._errors['password'] = 'Password is incorrect. Please try again.'
except User.DoesNotExist:
self._errors['username'] = 'User does not exist. Please use valid credentials.'
else:
self._errors['username'] = 'Please type a valid username.'
return super(login_form, self).clean(*args, **kwargs)
views.py:
from django.shortcuts import render, redirect
from django.http import HttpRequest, JsonResponse, HttpResponse
from django.template import RequestContext
from django.conf.urls import patterns
from django.contrib import admin
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.models import User, Group
from datetime import datetime
from .models import *
from .forms import *
from .functions import *
from os import *
import shutil
import json
def home(request):
if request.method == 'POST':
form = login_form(request.POST)
if form.is_valid():
username = form.data.get('username')
password = form.data.get('password')
authenticate_usr_obj = authenticate(username=username, password=password)
if authenticate_usr_obj is not None:
login(request, authenticate_usr_obj)
return redirect(dashboard)
else:
form = login_form()
try:
logout_successful = request.session.get('logout_complete', False)
request.session['logout_complete'] = False
except:
logout_successsful = False
context = {
'body_class': 'login',
'login': form,
'logout_successful': logout_successful
}
return render(request, 'development/index.html', context)
Just by reading throught Forms API docs I can see that in your example you did not clean data before you tried to get them in lines:
username = self.cleaned_data.get('username', None)
password = self.cleaned_data.get('password', None)
try to first use clean() to validate the data, like this:
cleaned_data = super(login_form, self).clean()
Then you can try to get them like this:
username = cleaned_data.get('username', None)
password = cleaned_data.get('password', None)
You can find more here
Version 2:
Ok, so first of all what I found in Django docs regarding the django.contrib.auth.password_validation.validate_password():
Integrating validation
validate_password(password, user=None, password_validators=None)
Validates a password. If all validators find the password valid, returns None. If one or more validators reject the password, raises a ValidationError with all the error messages from the validators.
The user object is optional: if it’s not provided, some validators may not be able to perform any validation and will accept any password.
So the line:
if password_validation.validate_password(password, user_obj) is None:
should be:
if password_validation.validate_password(password, user_obj) is not None:
Second of all:
Form.errors
>>>> f.errors
{'sender': ['Enter a valid email address.'], 'subject': ['This field is required.']}`
In this dictionary, the keys are the field names, and the values are lists of Unicode strings representing the error messages. The error messages are stored in lists because a field can have multiple error messages.
So based on that, I would change lines where you define your custom messages to look more something like this:
self.errors['password'] = [u'Password is incorrect. Please try again.']
And the one last thing to remember is that clean method must return the cleaned_data.
Final version of forms.py code could look something like that:
def clean(self):
super(login_form,self).clean()
username = self.cleaned_data.get('username', None)
password = self.cleaned_data.get('password', None)
if username is not None:
try:
user_obj = User.objects.get(username=username)
if password_validation.validate_password(password, user_obj) is not None:
self.errors['password'] = [u'Password is incorrect. Please try again.']
except User.DoesNotExist:
self.errors['username'] = [u'User does not exist. Please use valid credentials.']
else:
self.errors['username'] = [u'Please type a valid username.']
return self.cleaned_data
Hopefully it helps this time.
Tested your code above and I've encountered error due to forms self._errors
`Nonetype` object is not iterable
And here is my suggestion
forms.py
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
class login_form(forms.Form):
username = forms.CharField(
max_length=64, label='Username',
widget=forms.TextInput(
attrs={'placeholder': 'username...', 'class': 'login-field'}))
password = forms.CharField(
max_length=24, label='Password',
widget=forms.PasswordInput(
attrs={'placeholder': 'password...', 'class': 'login-field'}))
def clean_username(self):
data = self.cleaned_data.get('username', None)
if not data:
raise ValidationError('Please type a valid username.',
code='invalid')
return data
def clean(self, *args, **kwargs):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
try:
user_obj = User.objects.get(username=username)
if not user_obj.check_password(password):
raise ValidationError(
'Password is incorrect. Please try again',
code='incorrect')
except User.DoesNotExist:
raise ValidationError(
'User does not exist. Please use valid credentials.',
code='invalid')
return super(login_form, self).clean(*args, **kwargs)
views.py
from django.shortcuts import render
from django.contrib.auth import authenticate, login
from django.contrib.auth import logout
from .forms import *
def home(request):
logout_successful = True
if request.method == 'POST':
form = login_form(request.POST)
# print form.errors
if form.is_valid():
username = form.data.get('username')
password = form.data.get('password')
authenticate_usr_obj = authenticate(
username=username, password=password)
if authenticate_usr_obj:
login(request, authenticate_usr_obj)
# print 'ok'
else:
form = login_form()
logout_successful = request.user.is_authenticated()
return render(request, 'base.html', {
'body_class': 'login',
'login': form,
'logout_successful': logout_successful
})
def logout_view(request):
logout(request)
I'm still a beginner so feel free to comment something
Django newbie here.
I wrote simplified login form which takes email and password. It works great if both email and password are supplied, but if either is missing i get KeyError exception. According to django documentation this should never happen:
By default, each Field class assumes the value is required, so if you pass an empty value -- either None or the empty string ("") -- then clean() will raise a ValidationError exception
I tried to write my own validators for fields (clean_email and clean_password), but it doesn't work (ie I get KeyError exception). What am I doing wrong?
class LoginForm(forms.Form):
email = forms.EmailField(label=_(u'Your email'))
password = forms.CharField(widget=forms.PasswordInput, label=_(u'Password'))
def clean_email(self):
data = self.cleaned_data['email']
if not data:
raise forms.ValidationError(_("Please enter email"))
return data
def clean_password(self):
data = self.cleaned_data['password']
if not data:
raise forms.ValidationError(_("Please enter your password"))
return data
def clean(self):
try:
username = User.objects.get(email__iexact=self.cleaned_data['email']).username
except User.DoesNotExist:
raise forms.ValidationError(_("No such email registered"))
password = self.cleaned_data['password']
self.user = auth.authenticate(username=username, password=password)
if self.user is None or not self.user.is_active:
raise forms.ValidationError(_("Email or password is incorrect"))
return self.cleaned_data
You could leverage Django's built-in way to override how Authentication happens by setting
AUTHENTICATION_BACKENDS in your settings.py
Here's my EmailAuthBackend:
#settings.py
AUTHENTICATION_BACKENDS = (
'auth_backend.auth_email_backend.EmailBackend',
'django.contrib.auth.backends.ModelBackend',
)
#auth_email_backend.py
from django.contrib.auth.backends import ModelBackend
from django.forms.fields import email_re
from django.contrib.auth.models import User
class EmailBackend(ModelBackend):
"""
Authenticate against django.contrib.auth.models.User
"""
def authenticate(self, **credentials):
return 'username' in credentials and \
self.authenticate_by_username_or_email(**credentials)
def authenticate_by_username_or_email(self, username=None, password=None):
try:
user = User.objects.get(email=username)
except User.DoesNotExist:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
user = None
if user:
return user if user.check_password(password) else None
else:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
#forms.py
#replaces the normal username CharField with an EmailField
from django import forms
from django.contrib.auth.forms import AuthenticationForm
class LoginForm(AuthenticationForm):
username = forms.EmailField(max_length=75, label='Email')
next = forms.CharField(widget=forms.HiddenInput)
Hope that helps!
With Django 2.0 it's even simpler to achive this.
Create your own UserModel
class User(AbstractBaseUser, PermissionsMixin):
and set the USERNAME_FIELD = 'email'
For reference: Documentation