Handling 401 Unauthorized Error on Page Refresh with JWT and React/Django

204 views Asked by At

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;
0

There are 0 answers