Django Rest JWT login using username or email?
No need to write a custom authentication backend or custom login method.
A Custom Serializer inheriting JSONWebTokenSerializer, renaming the 'username_field' and overriding def validate() method.
This works perfectly for 'username_or_email' and 'password' fields where the user can enter its username or email and get the JSONWebToken for correct credentials.
from rest_framework_jwt.serializers import JSONWebTokenSerializerfrom django.contrib.auth import authenticate, get_user_modelfrom django.utils.translation import ugettext as _from rest_framework import serializersfrom rest_framework_jwt.settings import api_settingsUser = get_user_model()jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLERjwt_encode_handler = api_settings.JWT_ENCODE_HANDLERjwt_decode_handler = api_settings.JWT_DECODE_HANDLERjwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLERclass CustomJWTSerializer(JSONWebTokenSerializer): username_field = 'username_or_email' def validate(self, attrs): password = attrs.get("password") user_obj = User.objects.filter(email=attrs.get("username_or_email")).first() or User.objects.filter(username=attrs.get("username_or_email")).first() if user_obj is not None: credentials = { 'username':user_obj.username, 'password': password } if all(credentials.values()): user = authenticate(**credentials) if user: if not user.is_active: msg = _('User account is disabled.') raise serializers.ValidationError(msg) payload = jwt_payload_handler(user) return { 'token': jwt_encode_handler(payload), 'user': user } else: msg = _('Unable to log in with provided credentials.') raise serializers.ValidationError(msg) else: msg = _('Must include "{username_field}" and "password".') msg = msg.format(username_field=self.username_field) raise serializers.ValidationError(msg) else: msg = _('Account with this email/username does not exists') raise serializers.ValidationError(msg)
In urls.py:
url(r'{Your url name}$', ObtainJSONWebToken.as_view(serializer_class=CustomJWTSerializer)),
Building on top of Shikhar's answer and for anyone coming here looking for a solution for rest_framework_simplejwt (since django-rest-framework-jwt seems to be dead, it's last commit was 2 years ago) like me, here's a general solution that tries to alter as little as possible the original validation from TokenObtainPairSerializer
:
from rest_framework_simplejwt.serializers import TokenObtainPairSerializerclass CustomJWTSerializer(TokenObtainPairSerializer): def validate(self, attrs): credentials = { 'username': '', 'password': attrs.get("password") } # This is answering the original question, but do whatever you need here. # For example in my case I had to check a different model that stores more user info # But in the end, you should obtain the username to continue. user_obj = User.objects.filter(email=attrs.get("username")).first() or User.objects.filter(username=attrs.get("username")).first() if user_obj: credentials['username'] = user_obj.username return super().validate(credentials)
And in urls.py:
url(r'^token/$', TokenObtainPairView.as_view(serializer_class=CustomJWTSerializer)),
Found out a workaround.
@permission_classes((permissions.AllowAny,))def signin_jwt_wrapped(request, *args, **kwargs): request_data = request.data host = request.get_host() username_or_email = request_data['username'] if isEmail(username_or_email): # get the username for this email by model lookup username = Profile.get_username_from_email(username_or_email) if username is None: response_text = {"non_field_errors":["Unable to login with provided credentials."]} return JSONResponse(response_text, status=status.HTTP_400_BAD_REQUEST) else: username = username_or_email data = {'username': username, 'password':request_data['password']} headers = {'content-type': 'application/json'} url = 'http://' + host + '/user/signin_jwt/' response = requests.post(url,data=dumps(data), headers=headers) return JSONResponse(loads(response.text), status=response.status_code)
I check that whether the text that I received is a username or an email.
If email then I lookup the username for that and then just pass that to /signin_jwt/