Customize user authentication - django

Can anyone give more informations about customizing the Django authentication system. Well, I created a custom user in which I use the email as an identifier:
USERNAME_FIELD = 'email'
and I created a custom authentication backend, and I changed authentication backend to this new one in the settings file :
class Custom_Authentication(ModelBackend):
def authenticate(self, email=None, password=None):
try:
user = self.user_class.objects.get(email=email)
if user.check_password(password):
return user
except self.user_class.DoesNotExist:
return None
def get_user(self, user_id):
try:
return self.user_class.objects.get(pk=user_id)
except self.user_class.DoesNotExist:
return None
#property
def user_class(self):
if not hasattr(self, '_user_class'):
self._user_class = get_model(*settings.AUTH_USER_MODEL.split('.', 2))
if not self._user_class:
raise ImproperlyConfigured('Could not get custom user model')
return self._user_class
but now i don't know how to authenticate a custom user using his email, do I use :
from django.contrib import auth
def auth_view(request):
email = request.POST.get('email', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=email, password=password)
if user is not None:
auth.login(request, user)
return HttpResponseRedirect('/loggedin/')
else:
return HttpResponseRedirect('/invalid/')
I tried this and it seems that it doesn't recognize the user I created using the admin interface. Are there any other things that I have to change?

If you want to do a custom authentication I would suggest making a form for that. Make a form that takes email and password and then write the clean method to validate the two.
Try taking a look at this answer as an example.

Related

Django Rest Framework Testing

I have a LoginSerializer that has the block of code as below
def validate(self, attrs):
username = attrs.get('username', '')
password = attrs.get('password', '')
user = auth.authenticate(username=username, password=password)
if user:
if user.is_active is False:
raise AuthenticationFailed(
'Account is disabled, contact admin')
if not user.is_verified:
raise AuthenticationFailed('Email is not verified')
return {
'username': user.username,
'firstname': user.firstname,
'lastname': user.lastname,
'role': user.role,
'tokens': user.tokens
}
else:
raise AuthenticationFailed('Invalid credentials, try again')
and a test case as below;
class UserLoginTest(BaseTest):
def test_inactive_user_can_login(self):
self.client.post(
self.register_public, data=valid_user, format='json')
user = User.objects.get(username=valid_user['username'])
user.is_verified = True
user.is_active = False
user.save()
response = self.client.post(
self.login_url, valid_login_user, format='json')
print(response.data)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
When I run the test with is_active = False I get Invalid credentials, try again. Why is it that when is_active=False the user is not found even though the user is there? Same with when I try to login from swagger.
EDIT
I have read that I can use
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.AllowAllUsersModelBackend']
then I will be able to check for is_active manually otherwise django handles that and returns a None. What are the dangers of doing this?
This happens because you are using .authenticate() which by default goes through all backends listed in AUTHENTICATION_BACKENDS. If not listed in settings, django.contrib.auth.backends.ModelBackend is used, this backend verifies .is_active() field:
...
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
...
That snippet runs before your verification and returns None to user variable. So, it fails on if user condition (user is None) thus raise AuthenticationFailed('Invalid credentials, try again')
About AllowAllUsersModelBackend the only thing it does is override this method to allow inactive users to login:
class AllowAllUsersModelBackend(ModelBackend):
def user_can_authenticate(self, user):
return True
The only risk i can see, is using this backend and not checking .is_active() field manually. Unless if it is intended that inactive users can login into your system.

django have the ability to login with phone or email

I want to create a django custom user model such that i can have my users login with either phone or email.
This is my proposed solution
class ExtendedUser(AbstractBaseUser, PermissionsMixin):
phonenumber = PhoneNumberField(unique=True, null=True ..)
email = EmailField(unique=True, null=True ..)
...
USERNAME_FIELD = 'pk'
so now while login, i can do something like this
if cleaned_data['phonenumber']:
u = User.objects.get(phonenumber=cleaned_data['phonenumber'])
authenticate(username=u.pk, password=cleaned_data['password'])
...
elif cleaned_data['email']:
...
I am not sure whether it is possible to put USERNAME_FIELD as pk.
We can easily put a UUIDField if that's not possible.
Is the proposed solution fine?
The unique constrain for Email and Phone are good. Also, I would set USERNAME_FIELD = 'email'.
Then, I think you should try to create a custom authentication backend. You can check here
Like Django says: When somebody calls django.contrib.auth.authenticate(), Django tries authenticating across all of its authentication backends.
Then in your custom authentication backend you can ask for Email or Phone:
class CustomAuthenticationBackend:
def authenticate(self, request, email_or_phone=None, password=None):
try:
user = User.objects.get(
Q(email=email_or_phone) | Q(phone=email_or_phone)
)
pwd_valid = user.check_password(password)
if pwd_valid:
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
I think it can work. Let me know!!
Best regards.
django login take two argoman : reqeust and user query so when we get this two things we can login. django authenticate searching in the user model and when user in found ....
def log_in(request):
if request.user.is_authenticated is True:
return redirect('/home/')
if request.method == 'POST':
u = request.POST.get('_phone_')
p = request.POST.get('_pass1_')
if '#' in u:
user_check = authenticate(request, email=u, password=p)
else:
user_check = MyUser.objects.get(phone= u) # search in our model and find user with phone
if user_check is not None:
user_check.last_login= datetime.now()
user_check.save(update_fields=['last_login'])
login(request, user_check)
return redirect('/home/')
else:
return HttpResponse('bad request')
else:
return render(request, 'login.html')

Error about Django custom authentication and login?

I create a custom Authentication backends for my login system. Surely, the custom backends works when I try it in python shell. However, I got error when I run it in the server. The error says "The following fields do not exist in this model or are m2m fields: last_login". Do I need include the last_login field in customer model or Is there any other solution to solve the problem?
Here is my sample code:
In my models.py
class Customer(models.Model):
yes_or_no = ((True, 'Yes'),(False, 'No'))
male_or_female = ((True,'Male'),(False,'Female'))
name = models.CharField(max_length=100)
email = models.EmailField(max_length=100,blank = False, null = False)
password = models.CharField(max_length=100)
gender = models.BooleanField(default = True, choices = male_or_female)
birthday = models.DateField(default =None,blank = False, null = False)
created = models.DateTimeField(default=datetime.now, blank=True)
_is_active = models.BooleanField(default = False,db_column="is_active")
#property
def is_active(self):
return self._is_active
# how to call setter method, how to pass value ?
#is_active.setter
def is_active(self,value):
self._is_active = value
def __str__(self):
return self.name
In backends.py
from .models import Customer
from django.conf import settings
class CustomerAuthBackend(object):
def authenticate(self, name=None, password=None):
try:
user = Customer.objects.get(name=name)
if password == getattr(user,'password'):
# Authentication success by returning the user
user.is_active = True
return user
else:
# Authentication fails if None is returned
return None
except Customer.DoesNotExist:
return None
def get_user(self, user_id):
try:
return Customer.objects.get(pk=user_id)
except Customer.DoesNotExist:
return None
In views.py
#login_required(login_url='/dataInfo/login/')
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(name=username,password=password)
if user is not None:
if user.is_active:
login(request,user)
#redirect to user profile
print "suffcessful login!"
return HttpResponseRedirect('/dataInfo/userprofile')
else:
# return a disable account
return HttpResponse("User acount or password is incorrect")
else:
# Return an 'invalid login' error message.
print "Invalid login details: {0}, {1}".format(username, password)
# redirect to login page
return HttpResponseRedirect('/dataInfo/login')
else:
login_form = LoginForm()
return render_to_response('dataInfo/login.html', {'form': login_form}, context_instance=RequestContext(request))
In setting.py
AUTHENTICATION_BACKENDS = ('dataInfo.backends.CustomerAuthBackend', 'django.contrib.auth.backends.ModelBackend',)
This is happening because you are using django's login() function to log the user in.
Django's login function emits a signal named user_logged_in with the user instance you supplied as argument. See login() source.
And this signal is listened in django's contrib.auth.models. It tries to update a field named last_login assuming that the user instance you have supplied is a subclass of django's default AbstractUser model.
In order to fix this, you can do one of the following things:
Stop using the login() function shipped with django and create a custom one.
Disconnect the user_logged_in signal from update_last_login receiver. Read how.
Add a field named last_login to your model
Extend your model from django's base auth models. Read how
Thanks, I defined a custom login method as follows to get through this issue in my automated tests in which I by default keep the signals off.
Here's a working code example.
def login(client: Client, user: User) -> None:
"""
Disconnect the update_last_login signal and force_login as `user`
Ref: https://stackoverflow.com/questions/38156681/error-about-django-custom-authentication-and-login
Args:
client: Django Test client instance to be used to login
user: User object to be used to login
"""
user_logged_in.disconnect(receiver=update_last_login)
client.force_login(user=user)
user_logged_in.connect(receiver=update_last_login)
This in turn is used in tests as follows:
class TestSomething(TestCase):
"""
Scenarios to validate:
....
"""
#classmethod
#factory.django.mute_signals(signals.pre_save, signals.post_save)
def setUpTestData(cls):
"""
Helps keep tests execution time under control
"""
cls.client = Client()
cls.content_type = 'application/json'
def test_a_scenario(self):
"""
Scenario details...
"""
login(client=self.client, user=<User object>)
response = self.client.post(...)
...
Hope it helps.

User registration endpoint in Django Rest Framework

I'm using Django rest framework. I've written the following view to register a new user to the system.
#api_view(['POST'])
#csrf_exempt
#permission_classes((AllowAny, ))
def create_user(request):
email = request.DATA['email']
password = request.DATA['password']
try:
user = User.objects.get(email=email)
false = False
return HttpResponse(json.dumps({
'success': False,
'reason': 'User with this email already exists'
}), content_type='application/json')
except User.DoesNotExist:
user = User(email=email, username=email)
user.set_password(password)
user.save()
profile = UserProfile(user=user)
profile.save()
profile_serialized = UserProfileSerializer(profile)
token = Token(user=user)
token.save()
return HttpResponse(json.dumps({
'success': True,
'key': token.key,
'user_profile': profile_serialized.data
}), content_type='application/json')
Is there a better, slightly more secure way, of creating a user registration api in DRF that doesn't leave the endpoint so open to sql injection?
Forgive me to digress a little, but I can't help but wonder use could have gotten away with far less code, than you have if you had created a serializer and used a class-based view. Besides, if you had just created email as EmailField of serializer it would have automatically guaranteed the validation of email. Since you are using orm interface, risk of sql injection is much less than raw query in my opinion.
Sample Code:-
class UserList(CreateAPIView):
serializer_class = UserSerializer
class UserSerializer(ModelSerializer):
email = serializers.EmailField()
raw_password = serializers.CharField()
Something on these lines, Obviously I couldn't write entire code.
You could validate the email in your serializer using the validate-email-address module like this:
from validate_email_address import validate_email
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
def validate_email(self, value):
if not validate_email(value):
raise serializers.ValidationError("Not a valid email.")
return value
class Meta:
model = User
fields = ('email', 'password')
Also, you might consider a packaged auth/registration solution like Djoser.

Django custom login using own model

I am using Django1.4 with PostgreSQL. I am developing an application in which I have two models i.e. Students, Company.
class students(models.Model):
first_name = models.CharField(**option)
last_name = models.CharField(**option)
username = models.EmailField(max_length=100, unique=True)
password = models.CharField(_('password'), max_length=128)
# Some other attributes for Student models
class company(models.Model):
compnay_name = models.CharField(**option)
username = models.EmailField(max_length=100, unique=True)
password = models.CharField(_('password'), max_length=128)
#Some other attributes for company models
My Requirement:
Student and Company can create a new profile (provide a sign-up form)
Which creating a new profile for Student/Company, username i.e. email id should be unique. i.e. Email id should not exist in Student & Company models.(task completed )
Created 2 sign-In form for Student & Company login.
Issue:
As I am not using or extending User model, I am cannot use django in-built login & authenticate method.
How can I write a custom authentication method which should check user credentials in Student/Company username & password. (Have 2 different Sign-in form for Student & Company)
Please help me.
Thanks for reading my query.
backend.py
class LoginBackend:
def authenticate(self, username=None, password=None, model=None):
if model == "Student":
lookup_model = Student
elif model == "Employer":
lookup_model = Employer
try:
user = lookup_model.objects.get(email=username)
except Exception, e:
return None
return user
views.py
def check_auth(request):
user_object = Student.objects.get(email__iexact = unicode(email))
if check_password(password, user_object.password):
print authenticate(username = email, password = password, model = "Student")
login(request, user_object)
settings.py
AUTHENTICATION_BACKENDS = ("proj.app.backends.LoginBackend",)
Error
AttributeError at /xxx/login/
'Student' object has no attribute 'backend'
Write a custom authentication backend. Read this:
Writing an authentication backend
Handling authorization in custom backends
settings.AUTHENTICATION_BACKENDS
[update]
By writing and registering a custom authentication backend, you just have to use the standard Django authentication patterns. Looking at your sample code, I'm under the impression that you have understood it differently.
Since email is your unique key, I suggest using email for the login key, first check the login/password against Student, and if it fails, check against Company.
from django.contrib.auth.models import User
class JayapalsBackend(object):
def authenticate(self, username=None, password=None):
try:
o = Student.objects.get(email=username, password=password)
except Student.DoesNotExist:
try:
o = Company.objects.get(email=username, password=password)
except Company.DoesNotExist:
return None
return User.objects.get(email=o.email)
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Then just use standard Django decorators:
#login_required
def some_private_view(request):
...