I'm developing an authentication application using Django for the backend and React for the frontend. I've implemented JWT Tokens for authentication. When I log in, the HttpOnly cookies (access_token and refresh_token) are set correctly. However, I encounter a 401 (Unauthorized) error in the console when I refresh the page.
The error occurs during the GET request to http://localhost:8000/api/get-me/.
Issue
After successfully logging in, if I refresh the page, I get a 401 (Unauthorized) error in the console for the GET request to the /api/get-me/ endpoint. This behavior is unexpected since the user should still be authenticated after the page refresh.
Question
How can I ensure that the user remains authenticated even after refreshing the page, and avoid the 401 (Unauthorized) error? Is there something missing or incorrect in my configuration of JWT and cookie handling?
Relevant Code
urls.py
path('token/', views.LoginView.as_view(), name='token_obtain_pair'),
path('get-me/', views.GetUserDetailView.as_view(), name='get-me'),
path('token-validate/', views.TokenValidationView.as_view(), name='token-validate'),
views.py
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.permissions import IsAuthenticated
class TokenValidationView(APIView):
authentication_classes = [JWTAuthentication]
def get(self, request):
# If the code reaches here, it means the token is valid
return Response({"message": "Token is valid"}, status=status.HTTP_200_OK)
class GetUserDetailView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request):
serializer = UserDetailSerializer(request.user)
print(serializer.data)
return Response(serializer.data)
class LoginView(APIView):
def post(self, request, *args, **kwargs):
serializer = LoginUserSerializer(data=request.data)
if serializer.is_valid(raise_exception=True):
user = serializer.validated_data
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
response = Response({
'user_info': {
'id': user.id,
'first_name': user.first_name,
'last_name': user.last_name,
'email': user.email,
'is_email_verified': user.is_email_verified
}
}, status=status.HTTP_200_OK)
response.set_cookie(
'access_token',
access_token,
httponly=True,
max_age=int(refresh.access_token.lifetime.total_seconds()),
secure=True,
)
response.set_cookie(
'refresh_token',
str(refresh),
httponly=True,
max_age=int(refresh.lifetime.total_seconds()),
secure=True
)
return response
return Response(serializer.errors, status=400)
serializers.py
class LoginUserSerializer(serializers.Serializer):
email = serializers.EmailField()
password = serializers.CharField()
def validate(self, data):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("Incorrect Credentials")
App.js
const App = () => {
const dispatch = useDispatch();
const user = useSelector(selectUser); // Get the user from Redux store
useEffect(() => {
const validateTokenAndFetchUser = async () => {
try {
// Validate token
await axiosInstance.get('/api/token-validate/');
// If token is valid, fetch user info
const userInfoResponse = await axiosInstance.get('/get-me/');
console.log(userInfoResponse)
dispatch(loginUser({ state: userInfoResponse.data }));
} catch (error) {
console.error("Error in token validation or fetching user data:", error);
}
};
validateTokenAndFetchUser();
}, [dispatch]);
const handleLogin = async (email, password) => {
try {
const userData = await login(email, password);
console.log(userData);
dispatch(loginUser({ email: userData['email'], token: 'tralala' }));
} catch (error) {
console.error("Login failed:", error);
}
};
return (
<Router>
<Menu />
<div>
<Routes>
<Route
path="/login"
element={user ? <Navigate to="/" /> : <Login onLogin={handleLogin} />}
/>
<Route
path="/"
element={user ? <Dashboard user={user} /> : <Home />}
/>
</Routes>
</div>
</Router>
);
};
export default App;
axiosConfig.js
// axiosConfig.js
import axios from 'axios';
const axiosInstance = axios.create({
baseURL: 'http://localhost:8000/api/', // Replace with your API's base URL
headers: {
'Content-Type': 'application/json',
},
withCredentials: true, // This will ensure cookies are sent with the request
});
axiosInstance.interceptors.response.use(response => {
return response;
}, error => {
if (error.response && error.response.status === 401) {
console.log('Error 401')
}
return Promise.reject(error);
});
export default axiosInstance;