Djangorestframework-simplejwt: Revoke refresh token if password is changed

Created on 6 Jan 2019  路  9Comments  路  Source: jazzband/djangorestframework-simplejwt

If the user changes his/her password, the old refresh token can still be used to generate new access tokens.

How to make refresh tokens invalid if username or password is changed

Most helpful comment

I've ended up posting the refresh token to a DRF APIView while also removing it from the backend. If the token is in the headers, that may be a more straightforward approach.

// authAPI.js

static async logout() {
        try {
            const refresh_token = localStorage.getItem('refresh_token');
            const response = await axiosInstance.post('/api/user/token/blacklist/', {
                refresh_token: refresh_token
            });
            axiosInstance.defaults.headers['Authorization'] = null;
            return response
        } catch (error) {
            throw error;
        }
    }

And the Django code:

from rest_framework.views import APIView
from rest_framework_simplejwt.token_blacklist.models import OutstandingToken
from rest_framework import permissions, status

class LogoutAndBlacklistRefreshTokenForUserView(APIView):
    permission_classes = (permissions.AllowAny,)
    authentication_classes = ()

    def delete(self, request):
        # find all tokens by user and blacklists them, forcing them to log out.
        try:
            tokens = OutstandingToken.objects.filter(user=request.user)
            for token in tokens:
                token = RefreshToken(token.token)
                token.blacklist()
        except:
            token = RefreshToken(request.data.refresh_token)
            token.blacklist()
        return Response(status=status.HTTP_205_RESET_CONTENT)  # 204 means no content, 205 means no content and refresh

    def post(self, request):
        # Post is for logging out in current browser
        try:
            refresh_token = request.data["refresh_token"]
            token = RefreshToken(refresh_token)
            token.blacklist()
            return Response(status=status.HTTP_205_RESET_CONTENT)
        except:
            return Response(status=status.HTTP_400_BAD_REQUEST)

All 9 comments

In my opinion refresh token should be blacklisted in this case.
Also RefreshToken should be blacklisted in case of new authentication IMHO

It's not the best UX, but you could delete the tokens from local storage on the frontend and force the user to log in again.

Something like (In React-Redux):
localStorage.removeItem('refresh_token');

This is my custom implementation of blacklist on consequent logins:

from rest_framework_simplejwt.token_blacklist.models import BlacklistedToken, OutstandingToken


def logout(user):
    for token in OutstandingToken.objects.filter(user=user).exclude(
            id__in=BlacklistedToken.objects.filter(token__user=user).values_list('token_id', flat=True),
    ):
        BlacklistedToken.objects.create(token=token)


class BaseTokenObtainPairSerializer(simplejwt_serializers.TokenObtainPairSerializer):

    @classmethod
    def get_token(cls, user):
        logout(user=user)
        token = super(BaseTokenObtainPairSerializer, cls).get_token(user)
        return token

But the above method will blacklist all the refresh tokens of the user. What should I do to invalidate the current refresh token only?

@Aniket-Singla You can get the current token from _request.user_ I think. Then you can use the corresponding outstanding token to blacklist it.

However IMHO, all tokens should be blacklisted if user changes her password/username. I made a signal for this .

@adwait-thattey I already tried getting user from the request. But I did not find any way to only logout user from current device. Please suggest how to delete the current device token. I am trying to write logout functionality and not the password reset scenario.

I've ended up posting the refresh token to a DRF APIView while also removing it from the backend. If the token is in the headers, that may be a more straightforward approach.

// authAPI.js

static async logout() {
        try {
            const refresh_token = localStorage.getItem('refresh_token');
            const response = await axiosInstance.post('/api/user/token/blacklist/', {
                refresh_token: refresh_token
            });
            axiosInstance.defaults.headers['Authorization'] = null;
            return response
        } catch (error) {
            throw error;
        }
    }

And the Django code:

from rest_framework.views import APIView
from rest_framework_simplejwt.token_blacklist.models import OutstandingToken
from rest_framework import permissions, status

class LogoutAndBlacklistRefreshTokenForUserView(APIView):
    permission_classes = (permissions.AllowAny,)
    authentication_classes = ()

    def delete(self, request):
        # find all tokens by user and blacklists them, forcing them to log out.
        try:
            tokens = OutstandingToken.objects.filter(user=request.user)
            for token in tokens:
                token = RefreshToken(token.token)
                token.blacklist()
        except:
            token = RefreshToken(request.data.refresh_token)
            token.blacklist()
        return Response(status=status.HTTP_205_RESET_CONTENT)  # 204 means no content, 205 means no content and refresh

    def post(self, request):
        # Post is for logging out in current browser
        try:
            refresh_token = request.data["refresh_token"]
            token = RefreshToken(refresh_token)
            token.blacklist()
            return Response(status=status.HTTP_205_RESET_CONTENT)
        except:
            return Response(status=status.HTTP_400_BAD_REQUEST)

Thanks for helping @Toruitas . I will try to blacklist the token by getting it from the header.

I reached at the following logout method:

class LogoutView(views.APIView):
    permission_classes = (permissions.IsAuthenticated,)

    def post(self, request, *args, **kwargs):
        return self.logout_current_session(request)
def logout_current_session(self, request, *args, **kwargs):
        # Post is for logging out in current browser
        try:
            refresh_token = request.data["refresh_token"]
            token = OutstandingToken.objects.get(token=refresh_token)
            if token.user == request.user:
                BlacklistedToken.objects.create(token=token)
            return Response(status=status.HTTP_205_RESET_CONTENT)
        except:
            return Response(status=status.HTTP_400_BAD_REQUEST)

I am taking the refresh_token in post body. Token will be deleted only if the submitted token belongs to the current logged in user. Permission classes is also set to IsAuthenticated .
Thanks for the inputs.

Was this page helpful?
0 / 5 - 0 ratings