iMerica / dj-rest-auth

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

Impossible to refresh a JWT if JWT_AUTH_HTTPONLY is True #571

Open rostgoat opened 7 months ago

rostgoat commented 7 months ago

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.

Routhinator commented 7 months ago

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.

mohammadfayaj commented 6 months ago

@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

vi-klaas commented 6 months ago

+1

razllivan commented 5 months ago

+1

Aniket-Singla commented 5 months ago

@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.