You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在TypeScript项目中基于Axios与React Context实现规范的身份认证管理?

Hey there, let's work through your questions step by step with practical fixes for your React + TypeScript auth setup:

1. Do I need to call setToken() when the user logs in?

Absolutely—this is critical for tying your auth state to Axios interceptors. Here's how it all fits together:

  • When your login API call succeeds, it should return an access token (and ideally user info). You’ll need to call setToken(accessToken) to update the auth state.
  • Your existing request interceptor already watches the token state—once you set it, every subsequent Axios request will automatically include the Authorization: Bearer ${token} header, which is exactly what you want.
  • Pro tip: If your login endpoint returns user data alongside the token, you can immediately call setUser(userData) too, instead of making a separate /auth/authorize call right after login.

2. How to skip the /auth/authorize call on public routes (login/register)?

The issue right now is your useEffect runs every time the AuthProvider mounts, even on public pages where there’s no valid token (causing errors). Here’s how to fix it:

  1. Make sure your AuthProvider is wrapped inside your React Router component so it can access route information.
  2. Use useLocation to check the current path, and skip the /auth/authorize call if you’re on a public route.

Update your AuthProvider code to add this logic:

// Add this import at the top
import { useLocation } from 'react-router-dom';

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [token, setToken] = useState<string | null>(null);
  const [user, setUser] = useState<User | null>(null);
  const location = useLocation();
  // Define your public routes here
  const publicRoutes = ['/login', '/register'];

  useEffect(() => {
    const fetchMe = async () => {
      try {
        const { data } = await axios.get(
          "http://localhost:5000/auth/authorize",
          { withCredentials: true },
        );
        setUser(data.user);
      } catch (err) {
        setUser(null);
      }
    };

    // Only run the auth check if we're NOT on a public route
    if (!publicRoutes.includes(location.pathname)) {
      fetchMe();
    }
  }, [location.pathname]); // Re-run when the route changes

  // ... rest of your existing code
};

3. What’s the safest way to persist tokens between page refreshes?

You’re right to avoid localStorage—it’s vulnerable to XSS attacks since any JavaScript on the page can read it. The recommended approach is:

  • Refresh Token: Store this in an HttpOnly, Secure, SameSite=Strict cookie (set by your backend). This cookie can’t be accessed by frontend JS, so it’s safe from XSS.
  • Access Token: Keep this in React state (memory only). It will be lost on page refresh, but you can use the refresh token to get a new access token automatically.

Here’s how to adjust your code to implement this:

  1. Update the initial auth setup to try refreshing the token on page load (for non-public routes):
useEffect(() => {
  const initializeAuth = async () => {
    try {
      // First, use the refresh token (stored in HttpOnly cookie) to get a new access token
      const refreshRes = await axios.post(
        "/auth/refreshMyToken",
        {},
        { withCredentials: true }
      );
      const newToken = refreshRes.data.token;
      setToken(newToken);

      // Then fetch the user info with the new access token
      const { data } = await axios.get(
        "http://localhost:5000/auth/authorize",
        { headers: { Authorization: `Bearer ${newToken}` } }
      );
      setUser(data.user);
    } catch (err) {
      // Refresh failed—no valid session, reset state
      setToken(null);
      setUser(null);
    }
  };

  const publicRoutes = ['/login', '/register'];
  if (!publicRoutes.includes(location.pathname)) {
    initializeAuth();
  }
}, [location.pathname]);
  1. Fix your response interceptor to avoid infinite loops when refreshing tokens:
useLayoutEffect(() => {
  const responseInterceptor = axios.interceptors.response.use(
    (response) => response,
    async (error) => {
      const originalRequest = error.config;
      // Prevent infinite retry loops by marking requests that have already tried to refresh
      if (error.response?.status === 401 && !originalRequest._retry) {
        originalRequest._retry = true;
        try {
          const refreshRes = await axios.post(
            "/auth/refreshMyToken",
            {},
            { withCredentials: true }
          );
          const newToken = refreshRes.data.token;
          setToken(newToken);
          // Update the original request's auth header
          originalRequest.headers.Authorization = `Bearer ${newToken}`;
          // Retry the original request
          return axios(originalRequest);
        } catch (refreshErr) {
          // Refresh failed—log the user out
          logout();
          return Promise.reject(refreshErr);
        }
      }
      // For non-401 errors or failed refreshes, log out and reject
      logout();
      return Promise.reject(error);
    },
  );

  return () => {
    axios.interceptors.response.eject(responseInterceptor);
  };
}, []);

Full Modified Code

import axios from "axios";
import React, { createContext, useContext, useEffect, useLayoutEffect, useState } from "react";
import { useLocation } from 'react-router-dom';

type User = {
  id: string;
  username: string;
  email: string;
};

type AuthContextType = {
  user: User | null;
  token: string | null;
  setToken: (token: string | null) => void;
  logout: () => void;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [token, setToken] = useState<string | null>(null);
  const [user, setUser] = useState<User | null>(null);
  const location = useLocation();
  const publicRoutes = ['/login', '/register'];

  useEffect(() => {
    const initializeAuth = async () => {
      try {
        // Refresh token to get new access token
        const refreshRes = await axios.post(
          "/auth/refreshMyToken",
          {},
          { withCredentials: true }
        );
        const newToken = refreshRes.data.token;
        setToken(newToken);

        // Fetch user info with the new token
        const { data } = await axios.get(
          "http://localhost:5000/auth/authorize",
          { headers: { Authorization: `Bearer ${newToken}` } }
        );
        setUser(data.user);
      } catch (err) {
        setToken(null);
        setUser(null);
      }
    };

    if (!publicRoutes.includes(location.pathname)) {
      initializeAuth();
    }
  }, [location.pathname]);

  useLayoutEffect(() => {
    const requestInterceptor = axios.interceptors.request.use(
      (config) => {
        if (token && config.headers) {
          config.headers.Authorization = `Bearer ${token}`;
        }
        return config;
      },
      (error) => Promise.reject(error),
    );

    return () => {
      axios.interceptors.request.eject(requestInterceptor);
    };
  }, [token]);

  useLayoutEffect(() => {
    const responseInterceptor = axios.interceptors.response.use(
      (response) => response,
      async (error) => {
        const originalRequest = error.config;
        if (error.response?.status === 401 && !originalRequest._retry) {
          originalRequest._retry = true;
          try {
            const refreshRes = await axios.post(
              "/auth/refreshMyToken",
              {},
              { withCredentials: true }
            );
            const newToken = refreshRes.data.token;
            setToken(newToken);
            originalRequest.headers.Authorization = `Bearer ${newToken}`;
            return axios(originalRequest);
          } catch (refreshErr) {
            logout();
            return Promise.reject(refreshErr);
          }
        }
        logout();
        return Promise.reject(error);
      },
    );

    return () => {
      axios.interceptors.response.eject(responseInterceptor);
    };
  }, []);

  const logout = () => {
    setToken(null);
    setUser(null);
    // Optional: Call your backend logout endpoint to invalidate the refresh token
    axios.post("/auth/logout", {}, { withCredentials: true }).catch(() => {});
  };

  return (
    <AuthContext.Provider value={{ user, token, setToken, logout }}>
      {children}
    </AuthContext.Provider>
  );
};

export const useAuth = (): AuthContextType => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }
  return context;
};

内容的提问来源于stack exchange,提问作者JesSy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 10:17:28