From 11aff1de4da74ac00bf74b134262bc97442e10c8 Mon Sep 17 00:00:00 2001 From: boomchanotai Date: Fri, 17 May 2024 16:33:09 +0700 Subject: [PATCH 1/2] feat: refresh Token --- src/App.tsx | 80 +++++++++++++++++++++++++++++++++++-- src/layouts/AdminLayout.tsx | 17 +------- 2 files changed, 78 insertions(+), 19 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 46edc38c..e8fafaed 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,14 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { StrictMode, Suspense } from "react"; +import { + QueryCache, + QueryClient, + QueryClientProvider, +} from "@tanstack/react-query"; +import axios from "axios"; +import { StrictMode, Suspense, useState } from "react"; import { RouterProvider } from "react-router-dom"; import { router } from "./routes"; +import useAuthStore from "./store/authStore"; +import { calculateExpiryTime } from "./utils/calculateExpiryTime"; function AppRoot() { return ( @@ -18,7 +25,74 @@ function AppRoot() { } function App() { - const queryClient = new QueryClient(); + const accessToken = useAuthStore((state) => state.accessToken); + const refreshToken = useAuthStore((state) => state.refreshToken); + const setAuth = useAuthStore((state) => state.setAuth); + const clearAuth = useAuthStore((state) => state.clearAuth); + const [isRefreshingToken, setIsRefreshingToken] = useState(false); + + const refreshAuthToken = async ( + accessToken: string, + refreshToken: string + ) => { + if (isRefreshingToken) return; + if (accessToken == null || refreshToken == null) return; + + try { + const response = await axios.post( + `${import.meta.env.VITE_API_URL}/auth/refreshToken`, + { + refresh_token: refreshToken, + } + ); + + const expriedAt = calculateExpiryTime(response.data.expires_in); + setAuth( + response.data.access_token, + response.data.refresh_token, + expriedAt + ); + } catch (error) { + clearAuth(); + router.navigate("/admin"); + } finally { + setIsRefreshingToken(false); + } + }; + + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30000, // 30 seconds + retry: (failureCount, error) => { + // Don't retry for certain error responses + if ( + error.message.includes("400") || + error.message.includes("401") + ) { + return false; + } + + // Retry others just once + return failureCount <= 1; + }, + }, + }, + queryCache: new QueryCache({ + onError: (error) => { + if ( + error.message.includes("400") || + error.message.includes("401") + ) { + if (accessToken == null || refreshToken == null) return; + refreshAuthToken(accessToken, refreshToken); + } + }, + }), + }) + ); return ( diff --git a/src/layouts/AdminLayout.tsx b/src/layouts/AdminLayout.tsx index a29f152e..385a7dd1 100644 --- a/src/layouts/AdminLayout.tsx +++ b/src/layouts/AdminLayout.tsx @@ -1,30 +1,15 @@ import background from "@/assets/background/background.png"; import Footer from "@/components/Footer"; import Navbar from "@/components/Navbar"; -import useRefreshToken from "@/hooks/auth/useRefreshToken"; import useAuthStore from "@/store/authStore"; -import { useEffect } from "react"; import { Toaster } from "react-hot-toast"; -import { useNavigate } from "react-router-dom"; type MainLayoutProps = { children: React.ReactNode; }; const AdminLayout = ({ children }: MainLayoutProps) => { - const navigate = useNavigate(); - const { isLoggedIn, refreshToken, validateSession } = useAuthStore(); - const { mutate } = useRefreshToken(); - - useEffect(() => { - if (isLoggedIn == false) { - navigate("/admin"); - } - - if (isLoggedIn && refreshToken && validateSession()) { - mutate({ refresh_token: refreshToken }); - } - }, [isLoggedIn]); + const isLoggedIn = useAuthStore((state) => state.isLoggedIn); return ( <> From 3666ec1d0cda9e3790eb5f715a4bad18b35c9f04 Mon Sep 17 00:00:00 2001 From: boomchanotai Date: Fri, 17 May 2024 16:36:21 +0700 Subject: [PATCH 2/2] refactor: refresh token --- src/App.tsx | 17 +++--------- src/api/auth/refreshToken.ts | 8 ------ src/hooks/auth/useRefreshToken.ts | 43 ------------------------------- 3 files changed, 4 insertions(+), 64 deletions(-) delete mode 100644 src/hooks/auth/useRefreshToken.ts diff --git a/src/App.tsx b/src/App.tsx index e8fafaed..4c4eade1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,9 +3,9 @@ import { QueryClient, QueryClientProvider, } from "@tanstack/react-query"; -import axios from "axios"; import { StrictMode, Suspense, useState } from "react"; import { RouterProvider } from "react-router-dom"; +import { refreshToken as getRefreshToken } from "./api/auth/refreshToken"; import { router } from "./routes"; import useAuthStore from "./store/authStore"; import { calculateExpiryTime } from "./utils/calculateExpiryTime"; @@ -39,19 +39,10 @@ function App() { if (accessToken == null || refreshToken == null) return; try { - const response = await axios.post( - `${import.meta.env.VITE_API_URL}/auth/refreshToken`, - { - refresh_token: refreshToken, - } - ); + const response = await getRefreshToken(refreshToken); - const expriedAt = calculateExpiryTime(response.data.expires_in); - setAuth( - response.data.access_token, - response.data.refresh_token, - expriedAt - ); + const expriedAt = calculateExpiryTime(response.expires_in); + setAuth(response.access_token, response.refresh_token, expriedAt); } catch (error) { clearAuth(); router.navigate("/admin"); diff --git a/src/api/auth/refreshToken.ts b/src/api/auth/refreshToken.ts index cffe8f46..9a8e4d7a 100644 --- a/src/api/auth/refreshToken.ts +++ b/src/api/auth/refreshToken.ts @@ -1,4 +1,3 @@ -import useAuthStore from "@/store/authStore"; import axios from "axios"; interface RefreshTokenResponse { @@ -14,17 +13,10 @@ interface RefreshTokenCredentials { const refreshToken = async ( refresh_token: string ): Promise => { - const { accessToken } = useAuthStore.getState(); - const response = await axios.post( `${import.meta.env.VITE_API_URL}/auth/refreshToken`, { refresh_token, - }, - { - headers: { - Authorization: `Bearer ${accessToken}`, - }, } ); diff --git a/src/hooks/auth/useRefreshToken.ts b/src/hooks/auth/useRefreshToken.ts deleted file mode 100644 index 1e3555cc..00000000 --- a/src/hooks/auth/useRefreshToken.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - refreshToken, - RefreshTokenCredentials, - RefreshTokenResponse, -} from "@/api/auth/refreshToken"; -import useAuthStore from "@/store/authStore"; -import { calculateExpiryTime } from "@/utils/calculateExpiryTime"; -import { useMutation, UseMutationOptions } from "@tanstack/react-query"; -import { useNavigate } from "react-router-dom"; - -const useRefreshToken = () => { - const navigate = useNavigate(); - const { clearAuth } = useAuthStore(); - - const mutationOptions: UseMutationOptions< - RefreshTokenResponse, - Error, - RefreshTokenCredentials - > = { - mutationFn: (credentials: RefreshTokenCredentials) => - refreshToken(credentials.refresh_token), - onSuccess: (data: RefreshTokenResponse) => { - const expriedAt = calculateExpiryTime(data.expires_in); - useAuthStore - .getState() - .setAuth(data.access_token, data.refresh_token, expriedAt); - }, - onError: () => { - clearAuth(); - navigate("/admin"); - }, - }; - - const mutation = useMutation< - RefreshTokenResponse, - Error, - RefreshTokenCredentials - >(mutationOptions); - - return mutation; -}; - -export default useRefreshToken;