From e122f47c1c72332ed6a388c4b9bd13476ae9799f Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Wed, 4 Mar 2026 11:06:13 +0900 Subject: [PATCH 01/11] =?UTF-8?q?[FEAT]=20proxy=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/axios/axios.ts | 2 +- vite.config.ts | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index 11ad7892..30ae24d9 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -3,7 +3,7 @@ import { useLocalStorage } from "../../hooks/use-local-storage"; import { LOCAL_STORAGE_KEY } from "../../constants/key"; export const axiosInstance = axios.create({ - baseURL: import.meta.env.VITE_API_BASE_URL, + baseURL: import.meta.env.VITE_API_BASE_URL || "", }); // 요청 인터셉터: 모든 요청 전에 accessToken을 Authorization 헤더에 추가 diff --git a/vite.config.ts b/vite.config.ts index 4ff4f8fe..68fe53cd 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,8 +1,21 @@ -import { defineConfig } from "vite"; +import { defineConfig, loadEnv } from "vite"; import react from "@vitejs/plugin-react"; import tailwindcss from "@tailwindcss/vite"; // https://vite.dev/config/ -export default defineConfig({ - plugins: [react(), tailwindcss()], +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ""); + + return { + plugins: [react(), tailwindcss()], + server: { + proxy: { + // '/api'로 시작하는 모든 요청을 프록시가 가로챔 + "/api": { + target: env.VITE_API_BASE_URL, + changeOrigin: true, + }, + }, + }, + }; }); From 8174c8f15e2a98ec49536173f4499e76c19bd9a5 Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Thu, 5 Mar 2026 17:10:55 +0900 Subject: [PATCH 02/11] =?UTF-8?q?[REMOVE]=20SignupPage=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/auth/signup-page.tsx | 5 ----- src/routes/index.tsx | 5 ----- 2 files changed, 10 deletions(-) delete mode 100644 src/pages/auth/signup-page.tsx diff --git a/src/pages/auth/signup-page.tsx b/src/pages/auth/signup-page.tsx deleted file mode 100644 index f1f66d32..00000000 --- a/src/pages/auth/signup-page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -const SignupPage = () => { - return
SignupPage 입니다.
; -}; - -export default SignupPage; diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 63bd5b0c..6fd3921e 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -1,7 +1,6 @@ import { createBrowserRouter } from "react-router-dom"; import MobileLayout from "../layouts/mobile-layout"; import ProtectedRoute from "./protected-route"; -import SignupPage from "../pages/auth/signup-page"; import LoginPage from "../pages/auth/login-page"; import HomePage from "../pages/home/home-page"; import MyPage from "../pages/mypage/my-page"; @@ -47,10 +46,6 @@ export const router = createBrowserRouter([ path: "login/callback", element: , }, - { - path: "signup", - element: , - }, { path: "agreement", element: , From 332f594c850b5dacc41bc816250eed5a8c4fa5b0 Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Thu, 5 Mar 2026 17:30:10 +0900 Subject: [PATCH 03/11] =?UTF-8?q?[FEAT]=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20=EC=9D=B8=ED=84=B0=EC=85=89?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80&=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [FEAT] 리프레시 토큰 인터셉터 추가& 로그아웃 로직 --- src/api/auth.ts | 4 ---- src/api/axios/axios.ts | 43 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/api/auth.ts b/src/api/auth.ts index 34724ac0..ccbecb91 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -4,8 +4,6 @@ import type { LogoutResponse, ReissueResponse } from "../types/auth"; export const postReissue = async (): Promise => { const { data } = await axios.post( `${import.meta.env.VITE_API_BASE_URL}/api/auth/reissue`, - {}, - { withCredentials: true }, ); return data; }; @@ -13,8 +11,6 @@ export const postReissue = async (): Promise => { export const postLogout = async (): Promise => { const { data } = await axios.post( `${import.meta.env.VITE_API_BASE_URL}/api/auth/logout`, - {}, - { withCredentials: true }, ); return data; }; diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index 30ae24d9..85a2afaa 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -1,9 +1,15 @@ import axios from "axios"; import { useLocalStorage } from "../../hooks/use-local-storage"; import { LOCAL_STORAGE_KEY } from "../../constants/key"; +import { postLogout, postReissue } from "../auth"; +import toast from "react-hot-toast"; export const axiosInstance = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || "", + withCredentials: true, + headers: { + "Content-Type": "application/json", + }, }); // 요청 인터셉터: 모든 요청 전에 accessToken을 Authorization 헤더에 추가 @@ -24,3 +30,40 @@ axiosInstance.interceptors.request.use( // 요청 인터셉터가 실패하면, 에러 뿜음 (error) => Promise.reject(error), ); + +// 응답 인터셉터 +axiosInstance.interceptors.response.use( + (response) => response, + async (error) => { + const originalRequest = error.config; + + // 401에러 & 재시도 X + if (error.response?.status === 401 && !originalRequest._retry) { + originalRequest._retry = true; + + try { + const reissueRes = await postReissue(); + + originalRequest.headers.Authorization = `Bearer ${reissueRes.result.accessToken}`; + if (reissueRes.result.needAgreement) { + // 약관동의 안 돼있을 경우 약관동의로 리다이렉트 + window.location.href = "/agreement"; + } + return axiosInstance(originalRequest); + } catch (refreshError) { + // 리프레시 토큰 만료 + toast.error("세션이 만료되었습니다. 다시 로그인해주세요."); + + try { + await postLogout(); + } catch (logoutError) { + toast.error("로그아웃 API 호출 실패"); + } finally { + localStorage.removeItem(LOCAL_STORAGE_KEY.accessToken); + window.location.href = "/login"; + } + } + } + return Promise.reject(error); + }, +); From 43c12fd3f0000c5a0c72ddc4c0d3378a6efdcc0a Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Fri, 6 Mar 2026 13:53:46 +0900 Subject: [PATCH 04/11] =?UTF-8?q?[FEAT]=20=EB=A1=9C=EC=BB=AC=EA=B3=BC=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=ED=99=98=EA=B2=BD=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/axios/axios.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index 85a2afaa..5a5f3e3a 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -4,8 +4,11 @@ import { LOCAL_STORAGE_KEY } from "../../constants/key"; import { postLogout, postReissue } from "../auth"; import toast from "react-hot-toast"; +const isDev = import.meta.env.DEV; +const BASE_URL = isDev ? "" : import.meta.env.VITE_API_BASE_URL; + export const axiosInstance = axios.create({ - baseURL: import.meta.env.VITE_API_BASE_URL || "", + baseURL: BASE_URL, withCredentials: true, headers: { "Content-Type": "application/json", From 34a917759b318d014b9ec4feb52255e32734475e Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Fri, 6 Mar 2026 14:12:18 +0900 Subject: [PATCH 05/11] =?UTF-8?q?[FEAT]=20accessToken=20setItem=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/axios/axios.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index 5a5f3e3a..0ab42c6d 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -47,6 +47,8 @@ axiosInstance.interceptors.response.use( try { const reissueRes = await postReissue(); + const { setItem } = useLocalStorage(reissueRes.result.accessToken); + setItem(reissueRes.result.accessToken); originalRequest.headers.Authorization = `Bearer ${reissueRes.result.accessToken}`; if (reissueRes.result.needAgreement) { // 약관동의 안 돼있을 경우 약관동의로 리다이렉트 From fe69125b77fb80c268df66f199f356fd60ebf0a6 Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Fri, 6 Mar 2026 14:14:41 +0900 Subject: [PATCH 06/11] =?UTF-8?q?[REFACTOR]=20=EC=9D=BC=EA=B4=80=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20axiosInstance=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/api/auth.ts b/src/api/auth.ts index ccbecb91..0c1e8976 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,16 +1,13 @@ -import axios from "axios"; import type { LogoutResponse, ReissueResponse } from "../types/auth"; +import { axiosInstance } from "./axios/axios"; export const postReissue = async (): Promise => { - const { data } = await axios.post( - `${import.meta.env.VITE_API_BASE_URL}/api/auth/reissue`, - ); + const { data } = + await axiosInstance.post("/api/auth/reissue"); return data; }; export const postLogout = async (): Promise => { - const { data } = await axios.post( - `${import.meta.env.VITE_API_BASE_URL}/api/auth/logout`, - ); + const { data } = await axiosInstance.post("/api/auth/logout"); return data; }; From b55bb4259c846f6d70b0088e16f7b7ded0eeb00c Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Fri, 6 Mar 2026 14:31:35 +0900 Subject: [PATCH 07/11] =?UTF-8?q?[FEAT]=20=EC=95=BD=EA=B4=80=EB=8F=99?= =?UTF-8?q?=EC=9D=98=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89=ED=8A=B8?= =?UTF-8?q?=EC=8B=9C=20=EC=82=AC=EC=A0=84=EB=B0=98=ED=99=98&Error=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/axios/axios.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index 0ab42c6d..dde398ec 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -53,6 +53,7 @@ axiosInstance.interceptors.response.use( if (reissueRes.result.needAgreement) { // 약관동의 안 돼있을 경우 약관동의로 리다이렉트 window.location.href = "/agreement"; + return Promise.reject(new Error("Agreement required")); } return axiosInstance(originalRequest); } catch (refreshError) { From c1942f2a3f67f08fd9439248c76eb30cb41efb57 Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Mon, 9 Mar 2026 10:07:19 +0900 Subject: [PATCH 08/11] =?UTF-8?q?[REFACTOR]=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81=ED=95=B4=EC=84=9C=20let->const,?= =?UTF-8?q?=20axiosInstance=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth.ts | 15 +++++++++++---- src/api/axios/axios.ts | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/api/auth.ts b/src/api/auth.ts index 0c1e8976..c5367706 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,13 +1,20 @@ +import axios from "axios"; import type { LogoutResponse, ReissueResponse } from "../types/auth"; -import { axiosInstance } from "./axios/axios"; export const postReissue = async (): Promise => { - const { data } = - await axiosInstance.post("/api/auth/reissue"); + const { data } = await axios.post( + `${import.meta.env.VITE_API_BASE_URL}/api/auth/reissue`, + { + withCredentials: true, + }, + ); return data; }; export const postLogout = async (): Promise => { - const { data } = await axiosInstance.post("/api/auth/logout"); + const { data } = await axios.post( + `${import.meta.env.VITE_API_BASE_URL}/api/auth/logout`, + { withCredentials: true }, + ); return data; }; diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index dde398ec..3616b2a8 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -19,7 +19,7 @@ export const axiosInstance = axios.create({ axiosInstance.interceptors.request.use( (config) => { const { getItem } = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); - let accessToken = getItem(); + const accessToken = getItem(); // accessToken이 존재하면 Authorization 헤더에 Bearer 토큰 형식으로 추가한다 if (accessToken) { From c7312f5a793839cbbc050566c762d70856e4b9ee Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Mon, 9 Mar 2026 13:10:18 +0900 Subject: [PATCH 09/11] =?UTF-8?q?[MODIFY]=20auth.ts=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/auth.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/auth.ts b/src/api/auth.ts index c5367706..0ea7a7a0 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -4,6 +4,7 @@ import type { LogoutResponse, ReissueResponse } from "../types/auth"; export const postReissue = async (): Promise => { const { data } = await axios.post( `${import.meta.env.VITE_API_BASE_URL}/api/auth/reissue`, + {}, { withCredentials: true, }, @@ -14,6 +15,7 @@ export const postReissue = async (): Promise => { export const postLogout = async (): Promise => { const { data } = await axios.post( `${import.meta.env.VITE_API_BASE_URL}/api/auth/logout`, + {}, { withCredentials: true }, ); return data; From 887750ab403f2428ab5b692cbb7fe7b193ff55db Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Tue, 10 Mar 2026 20:30:02 +0900 Subject: [PATCH 10/11] =?UTF-8?q?[MODIFY]=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/axios/axios.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index 3616b2a8..ba74d7e7 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -9,7 +9,6 @@ const BASE_URL = isDev ? "" : import.meta.env.VITE_API_BASE_URL; export const axiosInstance = axios.create({ baseURL: BASE_URL, - withCredentials: true, headers: { "Content-Type": "application/json", }, From a11026756ab1f88296cf669f98779e26e8c73e65 Mon Sep 17 00:00:00 2001 From: tablemin03 Date: Tue, 10 Mar 2026 20:32:01 +0900 Subject: [PATCH 11/11] =?UTF-8?q?[MODIFY]=20=EC=BD=94=EB=93=9C=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/axios/axios.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/axios/axios.ts b/src/api/axios/axios.ts index ba74d7e7..751a8d81 100644 --- a/src/api/axios/axios.ts +++ b/src/api/axios/axios.ts @@ -46,7 +46,7 @@ axiosInstance.interceptors.response.use( try { const reissueRes = await postReissue(); - const { setItem } = useLocalStorage(reissueRes.result.accessToken); + const { setItem } = useLocalStorage(LOCAL_STORAGE_KEY.accessToken); setItem(reissueRes.result.accessToken); originalRequest.headers.Authorization = `Bearer ${reissueRes.result.accessToken}`; if (reissueRes.result.needAgreement) {