Django authentication with custom model - django

I have a custom login authentication with mysql table, while logging in how can I compare a hashed password with a plain-password in backends.py (Working fine with plain password)?
class MyBackEnd(object):
def authenticate(self, request, email=None, password=None):
existing_user = RegAuth.objects.get(email=email,password=password)
if not existing_user:
# Checking the user Regauth Custom DB.
user_data = RegAuth.objects.get(email=email,password=password)
if email == user_data.email:
user = RegAuth.objects.create_user(email=email, password=password)
user.save()
return user
else:
return None
else:
return existing_user
def get_user(self, email):
try:
return RegAuth.objects.get(email=email)
except Exception as e:
return False
Login view
def logauth(request):
if request.method == "POST":
email = request.POST['username']
password = request.POST['password']
user = authenticate(request, email=email, password=password)
if user is not None:
messages.error(request, 'if part : user is not None')
login(request, user)
return redirect('emp')
else:
messages.error(request, 'else part : user is None')
return redirect('login_url')
else:
messages.error(request, 'Please provide valid credentials')
return render(request, 'registration/login.html')

Is there any particular reason to diverge from Django's default authentication backend? I see at least a few issues with your authenticate method;
class MyBackEnd(object):
def authenticate(self, request, email=None, password=None):
# 1. password should not be used to retrieve a user, a pk should suffice
existing_user = RegAuth.objects.get(email=email,password=password)
if not existing_user:
# Checking the user Regauth Custom DB.
# 2. if the query before didn't yield results, why would it atp?
user_data = RegAuth.objects.get(email=email,password=password)
if email == user_data.email:
# 3. I'm not sure what flow validates this path, could you explain?
user = RegAuth.objects.create_user(email=email, password=password)
user.save()
return user
else:
return None
else:
return existing_user
For reference here's Django's default authenticate backend method (notice the use of except/else syntax):
def authenticate(self, request, username=None, password=None, **kwargs):
if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD)
try:
user = UserModel._default_manager.get_by_natural_key(username)
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)
else:
if user.check_password(password) and self.user_can_authenticate(user):
return user
It;
Tries to retrieve the user, then;
Checks the password, and;
Validates the user's state, then;
Returns the user object to the caller (successful authentication), or
Does NOT return a user to the caller. (failed authentication)
Alternately (no user found), it;
Instantiates a user object, and;
Sets the password using set_password (which hashes it), then;
Does NOT return a user to the caller. (failed authentication)
This alternate flow's usage of set_password is meant to mitigate some timing attacks see: https://code.djangoproject.com/ticket/20760
If you want to register new users, reset a user's password, or anything besides authentication, the authenticate method is not the right place.
I am aware this is not an answer to your question, but I hope it helps you and possibly others to understand the authentication flow and be critical about diverging from it.

Related

Django says current user is unauthenticated although logged in

I havent really changed anything and added a View to update some user data. Then i wanted to test my View and Django says that the current user is not authenticated. I logged out and logged in multiple Times and also looked at the requests in Burp. To me every looks fine, it always sends the session_id and also login goes through without problems. I also get my User Object returned to the frontend on login.
When i then try the "edit" function for the User then i'm unauthenticated...
This is my login:
#action(methods=['post'], detail=False, url_path='sign-in', url_name='sign-in')
def login_user(self, request):
email = str(request.data.get('email'))
password = str(request.data.get('password'))
if email is None or password is None:
return _ERROR_INCOMPLETE_CREDENTIALS
# User authentication...
user = authenticate_user(email=email, password=password)
if user is None:
return _ERROR_BAD_CREDENTIALS
user_profile = UserProfile.objects.get(id=user.id)
serialized_user = UserProfileSerializer([user_profile], many=True).data
print(serialized_user)
res = login(request, user)
print(res)
return Response(serialized_user, status=status.HTTP_200_OK)
This is the custom authenticate_user Method:
def authenticate_user(email, password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
This is a view which fails due to unauthentication:
#action(methods=['get'], detail=False, url_name='current', url_path='current')
def get_current_user(self, request):
if not request.user.is_authenticated:
return Response({"detail": "You need to be logged in for this!"}, status=status.HTTP_401_UNAUTHORIZED)
user = request.user
user_profile = UserProfile.objects.get(id=user.id)
return Response(UserProfileSerializer([user_profile], many=True).data)

Any Possibility to put condition on Password Required in Django Custom Auth?

I want the registered user to log in with the Email or PhoneNumber and the Password first. If the user forgot the Password then there should be the possibility to log in with OTP bypassing the Password which would be provided via SMS on the User Phone Number. So Is there any possibility to achieve that?
Here are official docs where the password field is always required.
https://docs.djangoproject.com/en/4.0/topics/auth/customizing/#a-full-example
I know we can change the username to the email or for a phone number if we want but how do we put the condition to login with Password/Random OTP. So how we can achieve that? a suggestion would be appreciated. Thanks
You can make your own CustomLoginBackend as
from django.contrib.auth import get_user_model
class CustomLoginBackend(object):
def authenticate(self, request, email, password, otp):
User = get_user_model()
try:
user = User.objects.using(db_name).get(email=email)
except User.DoesNotExist:
return None
else:
if password is not None:
if getattr(user, 'is_active', False) and user.check_password(password):
return user
else:
if getattr(user, 'is_active', False) and user.otp == otp: #<-- otp included in user table
return user
return None
Then in your login views.
from django.contrib.auth import authenticate, login
from django.contrib import messages
def login_view(request):
if request.method == 'POST':
email = request.POST.get('email', None)
password = request.POST.get('password', None)
otp = request.POST.get('otp', None)
user = authenticate(request, email=email, password=password, otp=otp)
if user is not None:
login(request, user)
# redirect to a success page
return redirect('dashboard')
else:
if password is not None:
# return either email or password incorrect
messages.error(request, "Invalid Email or Password")
return redirect('login')
else:
# return invalid otp
messages.error(request, "Invalid OTP")
return redirect('login')
return render(request, 'login.html')
And at last don't forgot to add AUTHENTICATION_BACKENDS in your settings.py as
AUTHENTICATION_BACKENDS = ['path_to_your.CustomLoginBackend ',]
Yes we can do that using forced login here is an example how i have did this please have a look i have a profile which is one to one relation with user
def login_otp(request):
mobile = request.session['mobile']
context = {'mobile':mobile}
if request.method == 'POST':
otp = request.POST.get('otp')
profile = Profile.objects.filter(mobile=mobile).first()
if otp == profile.otp:
user = User.objects.get(id = profile.user.id)
login(request , user)
return redirect('cart')
else:
context = {'message' : 'Wrong OTP' , 'class' : 'danger','mobile':mobile }
return render(request,'login_otp.html' , context)
return render(request,'login_otp.html' , context)

Django - Why do I need to specify the user.backend when logging in with a custom backend?

The code works as is, I'm just hoping somebody can provide an explanation here.
I set up a custom backend for my app. Code below:
from django.contrib.auth.backends import BaseBackend
from django.contrib.auth import get_user_model
class AuthenticationBackend(BaseBackend):
def authenticate(self, request, username=None, password=None, email=None):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=email)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
And here is the view:
def login_view(request):
form = LoginForm(request.POST or None)
if request.POST and form.is_valid():
user = form.login(request)
if user:
user.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, user)
print(request.user)
return redirect('tasks')
context = {
'form': form
}
return render(request, 'users/login/index.html', context)
Along with the form (Note much of the login functionality was abstracted to the form)
class LoginForm(forms.Form):
email = forms.CharField(max_length=255, required=True, widget=forms.TextInput(attrs={'class': 'form_input'}))
password = forms.CharField(widget=forms.PasswordInput(attrs={'class': 'form_input'}), required=True)
def clean(self):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
user = authenticate(email=email, password=password)
if not user or not user.is_active:
raise forms.ValidationError("Sorry, that login was invalid. Please try again.")
return self.cleaned_data
def login(self, request):
email = self.cleaned_data.get('email')
password = self.cleaned_data.get('password')
user = authenticate(email=email, password=password)
return user
In the login_view code, I had an issue. Before adding the "user.backend = ..." line, the system would login the user successfully, however upon redirecting to the 'tasks' view, the user would go back to Anonyomous.
After specifying the backend for the user prior to login, the code works fine.
Why do I need to specify the backend for the user prior to login? Is this because I'm using a custom backend? Did I mess up on the code somewhere?
Thank you in advanced!
Actually when you create more than one backend in the Django and register that backend in the Django settings Django is not able to identify for which backend this login functionality is so for that we have to specify the backend at login time or the other way is you can delete the default Django login backend but, if you delete that after that Django admin login might not work.

Django - user is_active

This is my user authentication method:
def user_login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user:
if user.is_active:
login(request, user)
return HttpResponseRedirect(reverse('index'))
else:
print('TEST')
messages.info(request, 'Inactive user')
return HttpResponseRedirect(reverse('index'))
else:
messages.error(request, 'Invalid username/password!')
return HttpResponseRedirect(reverse('index'))
else:
return render(request, 'mainapp/login.html', {})
If user exists and is not active wrong message appears:
messages.error(request, 'Invalid username/password!')
return HttpResponseRedirect(reverse('index'))
instead of:
print('TEST')
messages.info(request, 'Inactive user')
return HttpResponseRedirect(reverse('index'))
I don't have any idea what is wrong here... Any clues?
The default ModelBackend authentication backend started rejecting inactive users in Django 1.10. Therefore your authenticate() call returns None, and you get the Invalid username/password! message from the outer if/else statement.
As Daniel says, if you use the default ModelBackend, you no longer need to check user.is_active in your login view.
If you really want authenticate to return inactive users, then you can use AllowAllUsersModelBackend instead. If you do this, then it is your responsibility to check the is_active flag in your login view.
AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
The call to authenticate already checks that the user has the is_active flag set, and returns None if not. There is no need to check it separately.

Equivalent of get_or_create for adding users

Is there a simpler way to add a user than with the following pattern?
try:
new_user = User.objects.create_user(username, email, password)
except IntegrityError:
messages.info(request, "This user already exists.")
else:
new_user.first_name = first_name
# continue with other things
In Django 1.4, get_or_create() exists for User.
from django.contrib.auth.models import User
_user = User.objects.get_or_create(
username=u'bob',
password=u'bobspassword',
)
It is better not to catch IntegrityError as it can happen for other reasons. You need to check if user exists, excluding the password. If user already exists, set the password.
user, created = User.objects.get_or_create(username=username, email=email)
if created:
user.set_password(password)
user.save()
I don't think so. But you can proxify the Django User:
class MyUser(User):
class Meta:
proxy = True
def get_or_create(self, username, email, password, *args, **kwargs):
try:
new_user = User.objects.create_user(username, email, password)
except IntegrityError:
return User.objects.get(username=username, email=email)
else:
new_user.first_name = kwargs['first_name'] # or what you want
...etc...
return new_user
This method should solve this problem but keep in mind that in the database, password is kept as a hash of the password, and as pointed before, "get_or_create" makes an exact lookup. So before the lookup actually happens, we "pop" the password from the kwargs.
This method on your custom UserManager:
def get_or_create(self, defaults=None, **kwargs):
password = kwargs.pop('password', None)
obj, created = super(UserManager, self).get_or_create(defaults, **kwargs)
if created and password:
obj.set_password(password)
obj.save()
return obj, created