Search code examples
pythondjangodjango-rest-frameworkdjango-authenticationdjango-rest-framework-simplejwt

Adding custom user authentication to django-rest-framework-simple-jwt


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?


Solution

  • 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.