dj-rest-auth icon indicating copy to clipboard operation
dj-rest-auth copied to clipboard

Impossible to refresh a JWT if JWT_AUTH_HTTPONLY is True

Open rostgoat opened this issue 1 year ago • 5 comments

From the docs:

/dj-rest-auth/token/refresh/ (POST) (see also)

  • refresh Returns access

Note USE_JWT = True to use token/refresh/ route. Note Takes a refresh type JSON web token and returns an access type JSON web token if the refresh token is valid. HTTP 401 Unauthorized with {"detail": "Token is invalid or expired", "code": "token_not_valid"} in case of a invalid or expired token.

Then if you visit the (see also) link, here is what it says:

When this short-lived access token expires, you can use the longer-lived refresh token to obtain another access token:

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"}' \
  http://localhost:8000/api/token/refresh/

...
{"access":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNTY3LCJqdGkiOiJjNzE4ZTVkNjgzZWQ0NTQyYTU0NWJkM2VmMGI0ZGQ0ZSJ9.ekxRxgb9OKmHkfy-zs1Ro_xs1eMLXiR17dIDBVxeT-w"}

So what's the issue?

if JWT_AUTH_HTTPONLY is set to True, refresh returns an empty string when a login request is made. So how can one refresh an expired JWT if the above endpoint expects a refresh token to be present in the payload?

As described in the docs:

refresh_token will not be sent if JWT_AUTH_HTTPONLY set to True, set it to False if you need refresh_token.

rostgoat avatar Nov 19 '23 21:11 rostgoat

HTTPOnly cookies are sent with every request, and the very point is that Javascript cannot access them. Getting them in the response would defeat that.

Arguably it shouldn't return the access token either, as you shouldn't be using it. You need to store the expiration times and use them to determine if the credentials will still be valid, as well as appropriately handle 401 errors.

To refresh with JWT_HTTP_ONLY on, you post an empty body to the refresh endpoint and ensure your javascript is configured to send credentials (cookies) with the request. (In angular you add the parameter withCredentials: true to the http.post)

If you're past the expiration date of the access token or get a 401, post empty to refresh and store the updated access_expiration, and if you're past the refresh expiration date or get a 401 from refresh, then prompt the user for re-authentication.

Routhinator avatar Nov 20 '23 20:11 Routhinator

@rostgoat

/token/refresh View, accept the refresh_token on the body, not in the headers.

So, when you use a httponly flag in the cookies, it's no longer accessible by the Javascript. And for that, you can't add the refresh_token as a payload.

Note: If the httponly flag is enabled in the cookies, the browser automatically adds the cookies on every api request.

That's the main purpose of using a httponly flag

So the question is, if we can't access the cookies. How are we gonna send the refresh_token on the server and retrieve a new access_token ?

The answer is that you have to move the headers cookies to the payload

This is something like a preflight process. The client sent a request on the server with appropriate cookies headers, and when it was received by the server, It should extract the refresh_token from the request headers and added to payload. That's it !

You can archive this by simply creating a middleware.py and adding the middleware to your django settings.py

Here is the middleware code! https://github.com/iMerica/dj-rest-auth/issues/97#issuecomment-739942573

This issue was solved 4 years ago,

In 2024, it should be added to this project. Don't know why it has still not been added,

Thanks to @Wolf-Byte

mohammadfayaj avatar Jan 06 '24 08:01 mohammadfayaj

+1

vi-klaas avatar Jan 09 '24 17:01 vi-klaas

+1

razllivan avatar Jan 22 '24 20:01 razllivan

@rostgoat

/token/refresh View, accept the refresh_token on the body, not in the headers.

So, when you use a httponly flag in the cookies, it's no longer accessible by the Javascript. And for that, you can't add the refresh_token as a payload.

Note: If the httponly flag is enabled in the cookies, the browser automatically adds the cookies on every api request.

That's the main purpose of using a httponly flag

So the question is, if we can't access the cookies. How are we gonna send the refresh_token on the server and retrieve a new access_token ?

The answer is that you have to move the headers cookies to the payload

This is something like a preflight process. The client sent a request on the server with appropriate cookies headers, and when it was received by the server, It should extract the refresh_token from the request headers and added to payload. That's it !

You can archive this by simply creating a middleware.py and adding the middleware to your django settings.py

Here is the middleware code! #97 (comment)

This issue was solved 4 years ago,

In 2024, it should be added to this project. Don't know why it has still not been added,

Thanks to @Wolf-Byte

There is no need to add refresh token to request. The refresh cookie will directly be used from the cookie headers to produce a new access token for the user.

See https://github.com/iMerica/dj-rest-auth/blob/master/dj_rest_auth/jwt_auth.py#L84

I am using this functionality without any issues. I believe this particular issue should be closed.

Aniket-Singla avatar Jan 26 '24 05:01 Aniket-Singla