5

I created a Django app in which I want to be able to authenticate users by checking not only the username and password, but also a specific field in a related model. The custom request body I want to POST to the endpoint is:

payload = { 'username': user, 'password': password, 'app_id': uuid4}

I am using djangorestframework-simplejwt module to get the access token.

models.py

class Application(models.Model):

    app_name  = models.CharField(max_length=300)
    app_id    = models.UUIDField(default=uuid.uuid4, editable=False)

    def __str__(self):
        return self.app_name

class ProfileApp(models.Model):

    user       = models.OneToOneField(User, on_delete=models.CASCADE)
    app        = models.ForeignKey(Application, on_delete=models.CASCADE)
    expires_on = models.DateTimeField(default=datetime.now() + timedelta(days=15))

    def __str__(self):
        return self.app.app_name + " | "  + self.user.username

Is it possible to override the TokenObtainPairView from rest_framework_simplejwt to only authenticate an user if the expires_on date is still not expired? Or is there an architecture problem by doing it like this?

2
  • Does your urls file currently just use the default TokenObtainPairView.as_view()? Commented Jul 10, 2019 at 23:10
  • Yes, just using the default TokenObtainPairView.as_view() Commented Jul 11, 2019 at 9:23

1 Answer 1

9

You can do this by creating a custom serializer that inherits from TokenObtainPairSerializer, and extending the validate method to check for custom field values. There is no architecture problem if you are careful to not override necessary functionality of the parent class.

Here is an example:

import datetime as dt
import json
from rest_framework import exceptions
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework_simplejwt.views import TokenObtainPairView



class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
    def validate(self, attrs):
        try:
            request = self.context["request"]
        except KeyError:
            pass
        else:
            request_data = json.loads(request.body)
            username = request_data.get("username")
            app_id = request_data.get("app_id")

            profile_has_expired = False
            try:
                profile = ProfileApp.objects.get(user__username=username, app__app_id=app_id)
            except ProfileApp.DoesNotExist:
                profile_has_expired = True
            else:
                profile_has_expired = dt.date.today() > profile.expires_on
            finally:
                if profile_has_expired:
                    error_message = "This profile has expired"
                    error_name = "expired_profile"
                    raise exceptions.AuthenticationFailed(error_message, error_name)
        finally:
            return super().validate(attrs)


class MyTokenObtainPairView(TokenObtainPairView):
    serializer_class = MyTokenObtainPairSerializer

Then use MyTokenObtainPairView in place of TokenObtainPairView in your urls file.

Also since User and ProfileApp share a one-to-one field, it looks like you can get away with not using the "app_id" key/field at all.

Original source file: https://github.com/davesque/django-rest-framework-simplejwt/blob/04376b0305e8e2fda257b08e507ccf511359d04a/rest_framework_simplejwt/serializers.py

Sign up to request clarification or add additional context in comments.

1 Comment

With some adjustments (request.body to request.data) and making the datetime comparison timezone-aware , I've made it to work as desired. Thank you!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.