Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework Custom JWT authentication

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?

like image 495
drec4s Avatar asked Jul 10 '19 20:07

drec4s


1 Answers

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

like image 120
Henry Woody Avatar answered Nov 15 '22 12:11

Henry Woody