tokens refresh when jwt is expired and added basic axios config

This commit is contained in:
Kai Ritthaler 2025-06-25 07:56:30 +02:00 committed by Luisa Bellitto
parent c48498af95
commit fbf645ba0f
15 changed files with 470 additions and 289 deletions

View file

@ -0,0 +1,94 @@
import {
createContext,
useContext,
useState,
ReactNode,
FC,
useEffect,
} from "react";
import api from "./axios";
import { jwtDecode } from "jwt-decode";
import { redirectToLogin } from "./axios";
import { refreshToken } from "./refreshToken";
type User = {
id: string;
username: string;
role: string;
};
type AuthContextType = {
user: User | null;
logout: () => Promise<void>;
setUserState: (tryRefresh?: boolean) => Promise<void>;
};
type JwtPayload = {
username: string;
role: string;
sub: string;
jti: string;
iat: number;
exp: number;
iss: string;
};
const AuthContext = createContext<AuthContextType | null>(null);
type AuthProviderProps = {
children: ReactNode;
};
export const Auth: FC<AuthProviderProps> = ({ children }) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
setUserState();
}, []);
const setUserState = async (tryRefresh = true) => {
try {
const token = localStorage.getItem("token");
if (!token) return;
const now = Date.now() / 1000;
const decoded = jwtDecode<JwtPayload>(token);
if (decoded.exp > now) {
setUser({
id: decoded.sub,
username: decoded.username,
role: decoded.role,
});
} else if (tryRefresh) {
await refreshToken();
await setUserState(false);
}
} catch {
console.log("Error while reading and refreshing user info"); // this error should only appear if the app is run in strictMode
return;
}
};
const logout = async () => {
try {
await api.delete("/user/logout");
} catch {
console.log("Logout error");
}
localStorage.clear();
setUser(null);
redirectToLogin(false);
};
return (
<AuthContext.Provider value={{ user, logout, setUserState }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = (): AuthContextType => {
const ctx = useContext(AuthContext);
if (!ctx) {
throw new Error("useAuth must be used within an AuthProvider");
}
return ctx;
};

View file

@ -0,0 +1,67 @@
import axios from "axios";
import { refreshToken } from "./refreshToken";
const excludedUrls: string[] = ["/user/login", "/user/regiser"];
const api = axios.create({
baseURL: "http://localhost:3001/api",
withCredentials: true,
});
// get token from local storage
const getAccessToken = () => localStorage.getItem("token");
const getRefreshToken = () => localStorage.getItem("refreshToken");
//redirects the page to the login and back
export const redirectToLogin = (returnToPage = true) => {
if (returnToPage) {
const returnTo = window.location.pathname + window.location.search;
window.location.href = `/login?returnTo=${encodeURIComponent(returnTo)}`;
} else {
window.location.href = "/login";
}
};
// Request interceptor add token
api.interceptors.request.use((config) => {
const token = getAccessToken();
if (token && config.headers) {
config.headers["Authorization"] = `Bearer ${token}`;
}
return config;
});
// retry with new token
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
const isExcluded = excludedUrls.some((url) =>
originalRequest.url?.includes(url)
);
if (
error.response?.status === 401 &&
!originalRequest._retry &&
!isExcluded
) {
await refreshToken();
originalRequest._retry = true;
return api(originalRequest);
}
if (
error.response?.status === 401 &&
originalRequest._retry &&
!isExcluded
) {
localStorage.removeItem("token");
localStorage.removeItem("refreshToken");
redirectToLogin();
}
return Promise.reject(error);
}
);
export default api;

View file

@ -0,0 +1,28 @@
import axios from "axios";
const getRefreshToken = () => localStorage.getItem("refreshToken");
export const refreshToken = async () => {
const token = getRefreshToken();
if (!token) {
throw new Error("No refresh token available");
}
const response = await axios.get(
"http://localhost:3001/api/user/refreshToken",
{
headers: {
"Refresh-Token": getRefreshToken(),
},
withCredentials: true,
}
);
const authHeader = response.headers["authorization"];
if (authHeader && authHeader.startsWith("Bearer ")) {
const token = authHeader.substring(7);
localStorage.setItem("token", token);
}
const refreshToken = response.headers["refresh-token"];
if (refreshToken) {
localStorage.setItem("refreshToken", refreshToken);
}
};