4
votes

I want to add user login via One-time Password as well as the usual username/password method in django. In order to do so, either username/password or username/OTP are sent from client to sever and based on the provided pair of fields, I need to return access and refresh token if the user is authenticated. I am using django's simple-jwt. I know that I have to override TokenObtainPairView and TokenObtainSerializer. The problem is, I want to do the field validation part myself.

In my views, I override simple-jwt's default view.

#views.py

class MyTokenObtainPairView(TokenObtainPairView):
    serializer_class = MyTokenObtainPairSerializer

And I override the serializer like below:

#serializers.py

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):

    def validate(self, attrs):
        try:
            request = self.context["request"]
        except KeyError:
            pass

        try:
            request_data = json.loads(request.body)
            if("username" in request_data and "password" in request_data):
                # default scenario in simple-jwt  
                pass
            elif("username" in request_data and "otp" in request_data):                                   
                # validate username/otp manually and return access/token pair if successful
                pass

            else:
                # some fields were missing
                raise serializers.ValidationError({"username/otp or username/password" : "These fields are required"})

        except:
            pass

So, if client passes user credentials in one of the possible forms below, I will be able to authenticate it and return token pair.

{
   "username" : "Winston",
   "password" : "testpass"
}

or

{
    "username" : "Winston",
    "otp" : "testotp"
}

The problem is, when I send data in the second form, I get 400 BadRequest:password is required. How can I customize fields and their validation?

2
What is the reason to stick into single serializer? Alternative way to write 2 separate serializer for password and OTP.Saiful Azad

2 Answers

3
votes

As Saiful Azad mentioned in comments, one possible method is to use separate serializers for each scenario.

#views.py

class MyTokenObtainPairView(TokenObtainPairView):
    def get_serializer_class(self):
        if ("otp" in self.request.data):
            return MyTokenObtainPairSerializer
        return TokenObtainPairSerializer

Then, you can implement your own serializer for otp verification. I used simple-jwt's implementation to implement my own serializer and use my custom authentication method.

0
votes

In your urls.py

# Imports
from rest_framework_simplejwt.tokens import RefreshToken
from django.contrib.auth.models import User
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny

@api_view(['GET'])
@permission_classes([AllowAny])
def get_tokens_for_user(request):

    # find the user base in params
    user = User.objects.first()

    refresh = RefreshToken.for_user(user)

    return Response({ 
       'refresh': str(refresh),
       'access': str(refresh.access_token),
    })

urlpatterns = [
    path('login', get_tokens_for_user, name="login")
]