I am using django-graphql-jwt (https://django-graphql-jwt.domake.io/en/latest/index.html) to handle authentication for my Django Python Graphene application. Currently, everytime a new JWT generated, the previous JWT is still active as long as it does not pass its expiry time.
I want to revoke/prevent access to previously generated JWT (even if the JWT is not expired yet) whenever I generate a new JWT.
What I am thinking is utilizing the origIat inside the JWT payload and comparing it with something like a last_login attribute from the User model. I noticed though, that User.last_login is not updated whenever I am authenticating using JWT.
Still finding how to do this problem properly and wondering if there is any of you already solving this problem before.
Thanks!
My current solution:
class User(AbstractUser):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
email = models.EmailField(unique=True, null=False, blank=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
last_jwt_iat = models.DateTimeField(null=True, blank=True)
def __str__(self):
return f'{self.email}'
class ObtainJSONWebToken(graphql_jwt.relay.JSONWebTokenMutation):
@classmethod
def resolve(cls, root, info, **kwargs):
# Update User.last_jwt_iat by generating current UTC timestamp
# Probably better if we can extract the origIat from GraphQL payload
user = info.context.user
last_jwt_iat = datetime.datetime.utcnow()
user.last_jwt_iat = last_jwt_iat
user.save()
return cls()
def _authenticate(request):
"""
Check if user is anonymous and JWT authorization header exist
:param request: HttpRequest instance
:return: Boolean
"""
is_anonymous = not hasattr(request, 'user') or request.user.is_anonymous
return is_anonymous and get_http_authorization(request) is not None
class AuthCheckIat(object):
def resolve(self, next, root, info, **args):
context = info.context
# Check if User.last_jwt_iat > token iat, raise error, stop evaluation
if _authenticate(context):
jwt_decoded = jwt_decode(get_http_authorization(context))
username = jwt_decoded['username']
user = User.objects.get(username=username)
iat = datetime.datetime.fromtimestamp(jwt_decoded['origIat'], tz=datetime.timezone.utc)
if user.last_jwt_iat > iat:
raise PermissionDenied(
'User last JWT issued time is greater than supplied JWT issued time. Please use newer token. ')
return next(root, info, **args)