How to properly manage authentication with Axios and React Context in a TypeScript app?

1 day ago 1
ARTICLE AD BOX

I’m building a React + TypeScript app and using Axios for API requests. I have an AuthProvider using React Context to manage the user and token, and I’m running into a few questions and issues:

Do I need to use setToken() when the user logs in?
I’m not sure if I should store the token in state after login and how it should be used with Axios interceptors.

How to handle public routes like login/register?
My current AuthProvider runs a useEffect to fetch the current user (/auth/authorize) on app load, but this triggers errors on login/register because no token exists yet.
How can I prevent these effects from running on public pages while still fetching the user on protected pages?

How to securely store the token?
I know localStorage is an option, but I’ve heard it’s not very secure. What’s the recommended way to persist the token between page reloads in a secure manner?

import axios from "axios"; import React, { createContext, useContext, useEffect, useLayoutEffect, useState, } from "react"; 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); useEffect(() => { const fetchMe = async () => { try { const { data } = await axios.get( "http://localhost:5000/auth/authorize", { withCredentials: true, }, ); setUser(data.user); } catch (err) { setUser(null); } }; fetchMe(); }, []); 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) => { if (error.response?.status === 401) { const refreshRes = await axios.post( "/auth/refreshMyToken", {}, { withCredentials: true }, ); setToken(refreshRes.data.token); return axios(error.config); } logout(); return Promise.reject(error); }, ); return () => { axios.interceptors.response.eject(responseInterceptor); }; }, []); const logout = () => { setToken(null); setUser(null); }; return ( <AuthContext.Provider value={{ user, token, setToken, logout }}> {children} </AuthContext.Provider> ); }; // ----- useAuth hook ----- export const useAuth = (): AuthContextType => { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within an AuthProvider"); } return context; };
Read Entire Article