iMerica / dj-rest-auth

Authentication for Django Rest Framework
https://dj-rest-auth.readthedocs.io/en/latest/index.html
MIT License
1.63k stars 304 forks source link

[enhancement] Refresh view and Logout view doesn't work with http-only cookies #492

Open iboughtbed opened 1 year ago

iboughtbed commented 1 year ago

Logoutview

By default the library uses HTTP-only JWT cookies. In logoutview it doesn't take refresh token from cookies, but instead tries to take it from request body. So I can't put refresh token in body, because it's HTTP-only (not accessable by JavaScript). I searched for answers and one of them offers using middleware, but you can just edit the view (If i have mistakes, please let me know):

class LogoutView(BaseLogoutView):
    def logout(self, request):
        response = Response(
            {"detail": _("Successfully logged out.")},
            status=status.HTTP_200_OK,
        )

        cookie_name = getattr(api_settings, "JWT_AUTH_REFRESH_COOKIE", None)

        unset_jwt_cookies(response)

        try:
            if cookie_name and cookie_name in request.COOKIES:
                token = RefreshToken(request.COOKIES.get(cookie_name))
                token.blacklist()
        except KeyError:
            response.data = {"detail": _(
                "Refresh token was not included in request cookies.")}
            response.status_code = status.HTTP_401_UNAUTHORIZED
        except (TokenError, AttributeError, TypeError) as error:
            if hasattr(error, "args"):
                if "Token is blacklisted" in error.args or "Token is invalid or expired" in error.args:
                    response.data = {"detail": _(error.args[0])}
                    response.status_code = status.HTTP_401_UNAUTHORIZED
                else:
                    response.data = {"detail": _("An error has occurred.")}
                    response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
            else:
                response.data = {"detail": _("An error has occurred.")}
                response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
        return response

So, basically what I did is changed code to take token from cookies.

Refresh view

In refresh view it does set access-token cookie, but returns access token in the body of response. So I changed the code to:

class RefreshView(TokenRefreshView):
    serializer_class = CookieTokenRefreshSerializer

    def finalize_response(self, request, response, *args, **kwargs):
        if response.status_code == 200 and "access" in response.data:
            set_jwt_access_cookie(response, response.data["access"])
            response.data = {"detail": _("Successfully refreshed token.")}
            # or just add: response.data.clear(), because it returns access-token in the body of response
        return super().finalize_response(request, response, *args, **kwargs)
jonathancasters commented 1 year ago

@IBoughtBed I wonder why you don't want the refresh/access token in the response of the body when refreshing? Is this because that information won't be used anyway? Or is it for security reasons?

iboughtbed commented 1 year ago

@IBoughtBed I wonder why you don't want the refresh/access token in the response of the body when refreshing? Is this because that information won't be used anyway? Or is it for security reasons?

Yes it is for security reasons. As I said before It's using http only cookies. However, maybe they'll add some lines to check if http only cookies are enabled?