mirror of
https://github.com/ajurna/cbwebreader.git
synced 2026-11-02 10:36:55 +00:00
Implement cookie-based JWT authentication
- Introduced `CookieJWTAuthentication` for authentication via HttpOnly cookies, enabling browser-initiated requests without `Authorization` headers. - Added authentication views (`CookieTokenObtainPairView`, `CookieTokenRefreshView`, `CookieTokenBlacklistView`) to replace default SimpleJWT views. - Updated Vuex store and frontend logic to handle sessions using HttpOnly cookies instead of localStorage. - Enhanced backend and frontend configurations (Django settings, router, Vuex, and API service) to fully support cookie-based authentication. - Updated authentication tests to ensure correct cookie handling and authentication flows.
This commit is contained in:
1 parent
a7e9bd0743
commit
79c0647cc4
11 files changed
+369
-170
No files matched your search
@@ -0,0 +1,72 @@
|
||||
"""Token views that store JWTs in HttpOnly cookies (see comic/auth.py)."""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.exceptions import AuthenticationFailed
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework_simplejwt.exceptions import TokenError
|
||||
from rest_framework_simplejwt.serializers import TokenBlacklistSerializer, TokenRefreshSerializer
|
||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||
|
||||
from comic.auth import clear_auth_cookies, refresh_cookie_name, set_auth_cookies
|
||||
|
||||
|
||||
def _refresh_value(request: Request) -> str | None:
|
||||
"""The refresh token from the request body, or from the refresh cookie."""
|
||||
value = request.data.get('refresh') if isinstance(request.data, dict) else None
|
||||
return value or request.COOKIES.get(refresh_cookie_name())
|
||||
|
||||
|
||||
class CookieTokenObtainPairView(TokenObtainPairView):
|
||||
"""Log in: issue tokens and store them in HttpOnly cookies."""
|
||||
|
||||
def post(self, request: Request, *args: object, **kwargs: object) -> Response:
|
||||
response = super().post(request, *args, **kwargs)
|
||||
if response.status_code == status.HTTP_200_OK and isinstance(response.data, dict):
|
||||
set_auth_cookies(response, response.data)
|
||||
# The tokens live in HttpOnly cookies, so they are not returned to JavaScript.
|
||||
response.data = {}
|
||||
return response
|
||||
|
||||
|
||||
class CookieTokenRefreshView(TokenObtainPairView):
|
||||
"""Refresh using the refresh cookie and update both cookies."""
|
||||
|
||||
serializer_class = TokenRefreshSerializer
|
||||
|
||||
def post(self, request: Request, *args: object, **kwargs: object) -> Response:
|
||||
refresh = _refresh_value(request)
|
||||
if not refresh:
|
||||
return Response(status=status.HTTP_401_UNAUTHORIZED, data={'errors': 'No refresh token.'})
|
||||
|
||||
serializer = self.get_serializer(data={'refresh': refresh})
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
except TokenError as exc:
|
||||
raise AuthenticationFailed(exc.args[0]) from exc
|
||||
|
||||
response = Response(status=status.HTTP_200_OK)
|
||||
set_auth_cookies(response, serializer.validated_data)
|
||||
# Keep the cookies out of the body so JavaScript never sees the tokens.
|
||||
return response
|
||||
|
||||
|
||||
class CookieTokenBlacklistView(TokenObtainPairView):
|
||||
"""Log out: blacklist the refresh cookie and clear both cookies."""
|
||||
|
||||
serializer_class = TokenBlacklistSerializer
|
||||
|
||||
def post(self, request: Request, *args: object, **kwargs: object) -> Response:
|
||||
refresh = _refresh_value(request)
|
||||
if not refresh:
|
||||
return Response(status=status.HTTP_401_UNAUTHORIZED, data={'errors': 'No refresh token.'})
|
||||
|
||||
serializer = self.get_serializer(data={'refresh': refresh})
|
||||
try:
|
||||
serializer.is_valid(raise_exception=True)
|
||||
except TokenError as exc:
|
||||
raise AuthenticationFailed(exc.args[0]) from exc
|
||||
|
||||
response = Response(status=status.HTTP_204_NO_CONTENT)
|
||||
clear_auth_cookies(response)
|
||||
return response
|
||||
Reference in new issue
Block a user