Open jaycle opened 5 years ago
To implement this for now as a workaround, you can do the following.
Add this to views.py:
# views.py
from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView
from rest_framework_simplejwt.serializers import TokenRefreshSerializer
from rest_framework_simplejwt.exceptions import InvalidToken
class CookieTokenRefreshSerializer(TokenRefreshSerializer):
refresh = None
def validate(self, attrs):
attrs['refresh'] = self.context['request'].COOKIES.get('refresh_token')
if attrs['refresh']:
return super().validate(attrs)
else:
raise InvalidToken('No valid token found in cookie \'refresh_token\'')
class CookieTokenObtainPairView(TokenObtainPairView):
def finalize_response(self, request, response, *args, **kwargs):
if response.data.get('refresh'):
cookie_max_age = 3600 * 24 * 14 # 14 days
response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True )
del response.data['refresh']
return super().finalize_response(request, response, *args, **kwargs)
class CookieTokenRefreshView(TokenRefreshView):
def finalize_response(self, request, response, *args, **kwargs):
if response.data.get('refresh'):
cookie_max_age = 3600 * 24 * 14 # 14 days
response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True )
del response.data['refresh']
return super().finalize_response(request, response, *args, **kwargs)
serializer_class = CookieTokenRefreshSerializer
Change the urls in url.py to use those views for token obtaining and refreshing:
# url.py
from .views import CookieTokenRefreshView, CookieTokenObtainPairView # Import the above views
# [...]
urlpatterns = [
path('auth/token/', CookieTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('auth/token/refresh/', CookieTokenRefreshView.as_view(), name='token_refresh'),
# [...]
]
_Check your CORS settings if it doesn't work as expected: maybe you have to set sameSite and secure in setcookie
Workflow - obtain token pair using credentials
Workflow - obtain access (and optional refresh) token using refresh token
Please ref #360 and a specific comment made by stunaz ;) as I'm planning on shutting these ideas, issues, and PRs down.
To implement this for now as a workaround, you can do the following.
Add this to views.py:
# views.py from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView from rest_framework_simplejwt.serializers import TokenRefreshSerializer from rest_framework_simplejwt.exceptions import InvalidToken class CookieTokenRefreshSerializer(TokenRefreshSerializer): refresh = None def validate(self, attrs): attrs['refresh'] = self.context['request'].COOKIES.get('refresh_token') if attrs['refresh']: return super().validate(attrs) else: raise InvalidToken('No valid token found in cookie \'refresh_token\'') class CookieTokenObtainPairView(TokenObtainPairView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) class CookieTokenRefreshView(TokenRefreshView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) serializer_class = CookieTokenRefreshSerializer
I think you should override CookieTokenRefreshView
's serializer with CookieTokenRefreshSerializer
.
To implement this for now as a workaround, you can do the following.
Add this to views.py:
# views.py from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView from rest_framework_simplejwt.serializers import TokenRefreshSerializer from rest_framework_simplejwt.exceptions import InvalidToken class CookieTokenRefreshSerializer(TokenRefreshSerializer): refresh = None def validate(self, attrs): attrs['refresh'] = self.context['request'].COOKIES.get('refresh_token') if attrs['refresh']: return super().validate(attrs) else: raise InvalidToken('No valid token found in cookie \'refresh_token\'') class CookieTokenObtainPairView(TokenObtainPairView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) class CookieTokenRefreshView(TokenRefreshView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) serializer_class = CookieTokenRefreshSerializer
Change the urls in url.py to use those views for token obtaining and refreshing:
# url.py from .views import CookieTokenRefreshView, CookieTokenObtainPairView # Import the above views # [...] urlpatterns = [ path('auth/token/', CookieTokenObtainPairView.as_view(), name='token_obtain_pair'), path('auth/token/refresh/', CookieTokenRefreshView.as_view(), name='token_refresh'), # [...] ]
_Check your CORS settings if it doesn't work as expected: maybe you have to set sameSite and secure in setcookie
Workflow - obtain token pair using credentials
- POST /auth/token with valid credentials
- In the response body you'll notice that only the 'access' key is set
- The 'refresh' key has been moved to the httpOnly cookie named 'refresh_token'
Workflow - obtain access (and optional refresh) token using refresh token
- POST /auth/token/refresh with the cookie set from the previous workflow, the body can be empty
- In the response body you'll notice that only the 'access' key is set
- If you have set ROTATE_REFRESH_TOKENS, the httpOnly cookie 'refresh_token' contains a new refresh token
This looks well and good but is the CSRF token not necessary for methods like POST, PUT? here is an implementation of JWT + CSRF token + Cookies (HttpOnly) LocalStorage vs Cookies: All You Need To Know About Storing JWT Tokens Securely in The Front-End
To implement this for now as a workaround, you can do the following. Add this to views.py:
# views.py from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView from rest_framework_simplejwt.serializers import TokenRefreshSerializer from rest_framework_simplejwt.exceptions import InvalidToken class CookieTokenRefreshSerializer(TokenRefreshSerializer): refresh = None def validate(self, attrs): attrs['refresh'] = self.context['request'].COOKIES.get('refresh_token') if attrs['refresh']: return super().validate(attrs) else: raise InvalidToken('No valid token found in cookie \'refresh_token\'') class CookieTokenObtainPairView(TokenObtainPairView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) class CookieTokenRefreshView(TokenRefreshView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) serializer_class = CookieTokenRefreshSerializer
Change the urls in url.py to use those views for token obtaining and refreshing:
# url.py from .views import CookieTokenRefreshView, CookieTokenObtainPairView # Import the above views # [...] urlpatterns = [ path('auth/token/', CookieTokenObtainPairView.as_view(), name='token_obtain_pair'), path('auth/token/refresh/', CookieTokenRefreshView.as_view(), name='token_refresh'), # [...] ]
_Check your CORS settings if it doesn't work as expected: maybe you have to set sameSite and secure in setcookie Workflow - obtain token pair using credentials
- POST /auth/token with valid credentials
- In the response body you'll notice that only the 'access' key is set
- The 'refresh' key has been moved to the httpOnly cookie named 'refresh_token'
Workflow - obtain access (and optional refresh) token using refresh token
- POST /auth/token/refresh with the cookie set from the previous workflow, the body can be empty
- In the response body you'll notice that only the 'access' key is set
- If you have set ROTATE_REFRESH_TOKENS, the httpOnly cookie 'refresh_token' contains a new refresh token
This looks well and good but is the CSRF token not necessary for methods like POST, PUT? here is an implementation of JWT + CSRF token + Cookies (HttpOnly) LocalStorage vs Cookies: All You Need To Know About Storing JWT Tokens Securely in The Front-End
Hey, I was wondering if you could share how you modified these views and the frontend to implement CSRF as well.
@davesque Besides the refresh-token via cookie being more secure there is another important point. Jwt usecase is as follows: an authorization service + regular services. The former issues tokens, the latter use access tokens. What if there is multiple regular services and they belong to the same organization? And that organization doesn't want to ask the user to log-in multiple times?
So you have to pass the refresh token to every client (for every "regular service" that you have). And require the user to log-in multiple times. Which could be solved otherwise by setting a cookie for the auth service.
This issue has been open for going on four years.
1) Why is no one assigned? 2) Who is the point of contact for this issue on the project? 2) Why hasn’t this been implemented? 3) If it was implemented would it be merged?
This is a key feature for any server-side JWT library. I can’t imagine using JWT in the browser using anything but httpOnly cookies.
any update on this ?
any update on this? thanks.
Are you kidding me?! After about four years, this critical feature has not been added?! So what's the point of this whole JWT shit?! @Andrew-Chen-Wang
To implement this for now as a workaround, you can do the following.
Add this to views.py:
# views.py from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView from rest_framework_simplejwt.serializers import TokenRefreshSerializer from rest_framework_simplejwt.exceptions import InvalidToken class CookieTokenRefreshSerializer(TokenRefreshSerializer): refresh = None def validate(self, attrs): attrs['refresh'] = self.context['request'].COOKIES.get('refresh_token') if attrs['refresh']: return super().validate(attrs) else: raise InvalidToken('No valid token found in cookie \'refresh_token\'') class CookieTokenObtainPairView(TokenObtainPairView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) class CookieTokenRefreshView(TokenRefreshView): def finalize_response(self, request, response, *args, **kwargs): if response.data.get('refresh'): cookie_max_age = 3600 * 24 * 14 # 14 days response.set_cookie('refresh_token', response.data['refresh'], max_age=cookie_max_age, httponly=True ) del response.data['refresh'] return super().finalize_response(request, response, *args, **kwargs) serializer_class = CookieTokenRefreshSerializer
Change the urls in url.py to use those views for token obtaining and refreshing:
# url.py from .views import CookieTokenRefreshView, CookieTokenObtainPairView # Import the above views # [...] urlpatterns = [ path('auth/token/', CookieTokenObtainPairView.as_view(), name='token_obtain_pair'), path('auth/token/refresh/', CookieTokenRefreshView.as_view(), name='token_refresh'), # [...] ]
_Check your CORS settings if it doesn't work as expected: maybe you have to set sameSite and secure in setcookie
Workflow - obtain token pair using credentials
- POST /auth/token with valid credentials
- In the response body you'll notice that only the 'access' key is set
- The 'refresh' key has been moved to the httpOnly cookie named 'refresh_token'
Workflow - obtain access (and optional refresh) token using refresh token
- POST /auth/token/refresh with the cookie set from the previous workflow, the body can be empty
- In the response body you'll notice that only the 'access' key is set
- If you have set ROTATE_REFRESH_TOKENS, the httpOnly cookie 'refresh_token' contains a new refresh token
Refer to this
I commented about this last August, waited a month and then implemented my own JWT app. After reviewing drf-sjwt's code I came to the conclusion that it shouldn't be used in production. Even when I was looking for code to copy and paste as a starting point for a fork I couldn't find anything.
It's been a few months so I can't point to all the questionable code, but, look for yourself. Here's a "for example" tho: drf-sjwt's config code uses a non-public DRF code that DRF explicitly flags as internal: Here's the note from DRF and here's where it's used in drf-sjwt. It looked like it was working but I was able to break it during testing.
My advice, roll you own. It took me about three months to create/test configurable auth/RBAC/ABAC+registration apps based on simplejwt. It wasn't bad.
It's been years. Any update?
@MohammadSalek After reading the source and taking into account issues like this one, I came to the conclusion that djangorestframework-simplejwt
should not be used in production and built my own JWT auth library. Even something as fundamental as the way drf-simplejwt
handles settings disregards the explicit warnings of the DRF project.
See my comment above for more details.
any update on this? thanks.
Similar to #23 but with a different motivation.
To protect against XSS, I would like the option to store the JWT in an HttpOnly cookie.
django-rest-framework-jwt
has this feature as an optional setting but that project I believe is abandoned and also has a vulnerability due to preventing the usage of django's CSRF token (see: https://github.com/GetBlimp/django-rest-framework-jwt/pull/434). Combining an HttpOnly cookie with CSRF token would be a pretty rock solid solution.References: https://stormpath.com/blog/where-to-store-your-jwts-cookies-vs-html5-web-storage https://stackoverflow.com/questions/44133536/is-it-safe-to-store-a-jwt-in-localstorage-with-reactjs