From 373c2b1815c53ba0297cc2b0dfd31f0e03fa8705 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 10 Apr 2026 18:11:15 +0530 Subject: [PATCH] feat: implement secure cookie-based authentication with login, refresh, and logout endpoints --- backend/users/tests.py | 31 ++++++++++++++++++++++++++++++- backend/users/urls.py | 9 ++++----- backend/users/views.py | 19 +++++++++++++++++++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/backend/users/tests.py b/backend/users/tests.py index a39b155..73540ef 100644 --- a/backend/users/tests.py +++ b/backend/users/tests.py @@ -1 +1,30 @@ -# Create your tests here. +from django.conf import settings +from django.contrib.auth import get_user_model +from django.urls import reverse +from rest_framework import status +from rest_framework.test import APITestCase + +User = get_user_model() + + +class AuthTests(APITestCase): + def setUp(self): + self.password = "password123" + self.user = User.objects.create_user( + email="test@example.com", password=self.password, full_name="Test User", is_active=True + ) + self.login_url = reverse("token_obtain_pair") + self.refresh_url = reverse("token_refresh") + self.logout_url = reverse("logout") + + def test_login_sets_secure_cookie(self): + data = {"email": self.user.email, "password": self.password} + response = self.client.post(self.login_url, data) + cookie_name = settings.SIMPLE_JWT["AUTH_COOKIE"] + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn("access", response.data) + self.assertNotIn("refresh", response.data) + self.assertIn(cookie_name, response.cookies) + # verify the cookie has a value + self.assertTrue(response.cookies[cookie_name].value) diff --git a/backend/users/urls.py b/backend/users/urls.py index 68b0bbe..5d3f8f1 100644 --- a/backend/users/urls.py +++ b/backend/users/urls.py @@ -1,18 +1,17 @@ from django.urls import path -from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView -from users.views import ActivationView - -from .views import MeView, RegisterView +from .views import ActivationView, LogoutView, MeView, RegisterView, TokenLoginView, TokenRefreshView urlpatterns = [ path("register/", RegisterView.as_view(), name="register"), # Login and get access and refresh tokens - path("login/", TokenObtainPairView.as_view(), name="token_obtain_pair"), + path("login/", TokenLoginView.as_view(), name="token_obtain_pair"), # Get a new access token using a refresh token path("refresh/", TokenRefreshView.as_view(), name="token_refresh"), # Get current user info path("me/", MeView.as_view(), name="me"), # Activate user account path("activate///", ActivationView.as_view(), name="activate"), + # Logout and delete the access token + path("logout/", LogoutView.as_view(), name="logout"), ] diff --git a/backend/users/views.py b/backend/users/views.py index bd2f99a..6fcc6ba 100644 --- a/backend/users/views.py +++ b/backend/users/views.py @@ -55,6 +55,8 @@ class MeView(generics.RetrieveAPIView): class TokenLoginView(TokenObtainPairView): + permission_classes = (permissions.AllowAny,) + def post(self, request, *args, **kwargs): response = super().post(request, *args, **kwargs) if response.status_code == status.HTTP_200_OK: @@ -64,6 +66,8 @@ class TokenLoginView(TokenObtainPairView): class RefreshTokenView(TokenRefreshView): + permission_classes = (permissions.IsAuthenticated,) + def post(self, request, *args, **kwargs): refresh_token = request.COOKIES.get(settings.SIMPLE_JWT["AUTH_COOKIE"]) if not refresh_token: @@ -74,3 +78,18 @@ class RefreshTokenView(TokenRefreshView): new_refresh_token = response.data["refresh"] response = set_response_cookies(response, new_refresh_token) return response + + +class LogoutView(generics.GenericAPIView): + permission_classes = (permissions.AllowAny,) + + def post(self, request): + response = Response({"detail": "Successfully logged out"}, status=status.HTTP_200_OK) + # Clear the secure cookie + response.delete_cookie( + key=settings.SIMPLE_JWT["AUTH_COOKIE"], + domain=settings.SIMPLE_JWT.get("AUTH_COOKIE_DOMAIN"), + samesite=settings.SIMPLE_JWT.get("AUTH_COOKIE_SAMESITE"), + path="/", + ) + return response