In django-rest-framework-simplejwt plugin username
and password
are used by default. But I wanted to use email
instead of username
. So, I did like below:
In serializer:
class MyTokenObtainSerializer(Serializer):
username_field = User.EMAIL_FIELD
def __init__(self, *args, **kwargs):
super(MyTokenObtainSerializer, self).__init__(*args, **kwargs)
self.fields[self.username_field] = CharField()
self.fields['password'] = PasswordField()
def validate(self, attrs):
# self.user = authenticate(**{
# self.username_field: attrs[self.username_field],
# 'password': attrs['password'],
# })
self.user = User.objects.filter(email=attrs[self.username_field]).first()
print(self.user)
if not self.user:
raise ValidationError('The user is not valid.')
if self.user:
if not self.user.check_password(attrs['password']):
raise ValidationError('Incorrect credentials.')
print(self.user)
# Prior to Django 1.10, inactive users could be authenticated with the
# default `ModelBackend`. As of Django 1.10, the `ModelBackend`
# prevents inactive users from authenticating. App designers can still
# allow inactive users to authenticate by opting for the new
# `AllowAllUsersModelBackend`. However, we explicitly prevent inactive
# users from authenticating to enforce a reasonable policy and provide
# sensible backwards compatibility with older Django versions.
if self.user is None or not self.user.is_active:
raise ValidationError('No active account found with the given credentials')
return {}
@classmethod
def get_token(cls, user):
raise NotImplemented(
'Must implement `get_token` method for `MyTokenObtainSerializer` subclasses')
class MyTokenObtainPairSerializer(MyTokenObtainSerializer):
@classmethod
def get_token(cls, user):
return RefreshToken.for_user(user)
def validate(self, attrs):
data = super(MyTokenObtainPairSerializer, self).validate(attrs)
refresh = self.get_token(self.user)
data['refresh'] = text_type(refresh)
data['access'] = text_type(refresh.access_token)
return data
In view:
class MyTokenObtainPairView(TokenObtainPairView):
"""
Takes a set of user credentials and returns an access and refresh JSON web
token pair to prove the authentication of those credentials.
"""
serializer_class = MyTokenObtainPairSerializer
And it works!!
Now my question is, how can I do it more efficiently? Can anyone give suggestion on this? Thanks in advance.
So we can use that email address as the primary ‘user identifier’ instead of a username for authentication. Default Django app will give you a User Model that has a mandatory username field, and an ‘optional’ email field. However if you’re starting a new project, Django highly recommends you to set up a custom User Model.
For example, when you create a Google account, the first mail you get would be something like, “Hi Xyz, Welcome to Google. Your new account comes with access to Google products, apps, and services…..” Sending these types of emails from your Django application is quite easy.
Default Django app will give you a User Model that has a mandatory username field, and an ‘optional’ email field. However if you’re starting a new project, Django highly recommends you to set up a custom User Model. For more info on this subkect look at official doc: Custom User Model
Django comes with built-in authentication views but they require manual configuration including urls and templates for a complete sign up page. So while we can roll our own a better approach--and the current default for many professionals--is to use the django-allauth package instead.
This answer is for future readers and therefore contains extra information.
In order to simplify the authentication backend, you have multiple classes to hook into. I would suggest to do option 1 (and optionally option 3, a simplified version of yours) below. Couple of notes before you read on:
User.objects.filter(email__iexact=...)
to match the emails in a case insensitive way.get_user_model()
in case you replace the default user model in future, this really is a life-saver for beginners!As for the 3 options:
class EmailModelBackend(ModelBackend)
and replace authenticate function.
authenticate(username=, password=, **kwarg)
from django.contrib.auth
authenticate(...)
, only replaces JWT auth (if you set it up as such)
parameters is not required and therefore this option is less adviced).MyTokenObtainPairSerializer
with email as claim.
Option 1 (note that this also allows username!!):
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend
from django.db.models import Q
class EmailorUsernameModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(Q(username__iexact=username) | Q(email__iexact=username))
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None
Option 2: Skipped, left to reader and not adviced.
Option 3: You seem to have this covered above.
Note: you dont have to define MyTokenObtainPairView
, you can use TokenObtainPairView(serializer_class=MyTokenObtainPairSerializer).as_view()
in your urls.py. Small simplification which overrides the used token serializer.
Note 2: You can specify the identifying claim and the added data in your settings.py (or settings file) to use email as well. This will make your frontend app use the email for the claim as well (instead of default user.id)
SIMPLE_JWT = {
'USER_ID_FIELD': 'id', # model property to attempt claims for
'USER_ID_CLAIM': 'user_id', # actual keyword in token data
}
However, heed the uniqueness warnings given by the creators:
For example, specifying a "username" or "email" field would be a poor choice since an account's username or email might change depending on how account management in a given service is designed.
If you can guarantee uniqueness, you are all set.
And in addition to @Mic's answer, remember to set USERNAME_FIELD = 'email'
and may be REQUIRED_FIELDS = ['username']
in the User model.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With