feat: refresh 로직 수정

This commit is contained in:
2025-03-12 13:29:34 +09:00
parent 6e41c09157
commit 108e2a8746
9 changed files with 154 additions and 24 deletions
+28 -9
View File
@@ -9,21 +9,21 @@ import {
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export async function POST(
async function handler(
request: Request,
props: { params: Promise<{ url: string[] }> },
params: { url: string[] },
method: "GET" | "POST" | "PUT" | "DELETE",
) {
const params = await props.params;
const cookieStore = await cookies();
const headers: Record<string, string> = {
"Content-Type": "application/json",
"X-Site-Id": SITE_ID,
};
const cookieStore = await cookies();
let body = {};
const isRefreshHandler = params.url.includes("refresh");
// silent refresh
if (params.url.includes("refresh")) {
if (isRefreshHandler) {
const refreshToken = cookieStore.get(REFRESH_TOKEN);
headers[CLAIM_NAME] = refreshToken?.value ?? "";
@@ -33,9 +33,12 @@ export async function POST(
}
try {
const body = await request.json();
if (!isRefreshHandler) {
body = await request.json();
}
const result = await fetch(`${SERVER_API_URL}/${params.url.join("/")}`, {
method: "POST",
method,
headers,
body: JSON.stringify(body),
});
@@ -79,3 +82,19 @@ export async function POST(
);
}
}
export async function POST(
request: Request,
props: { params: Promise<{ url: string[] }> },
) {
const params = await props.params;
return handler(request, params, "POST");
}
export async function PUT(
request: Request,
props: { params: Promise<{ url: string[] }> },
) {
const params = await props.params;
return handler(request, params, "PUT");
}
+4 -2
View File
@@ -1,13 +1,15 @@
import "./styles/base.css";
import "./styles/components.css";
import { CookieProvider, QueryProvider } from "@/shared/provider";
import { AuthProvider, CookieProvider, QueryProvider } from "@/shared/provider";
const App = ({ children }: Readonly<{ children: React.ReactNode }>) => {
return (
<html lang="ko">
<body>
<QueryProvider>
<CookieProvider>{children}</CookieProvider>
<CookieProvider>
<AuthProvider>{children}</AuthProvider>
</CookieProvider>
</QueryProvider>
</body>
</html>
@@ -31,7 +31,7 @@ export class LoginApiService implements LoginService {
const tokenId = data[AUTH_USER_ID];
if (!accessToken || !tokenId) {
throw new Error("Missing required authentication tokens");
throw new Error("[login] Missing required authentication tokens");
}
tokenManager.setToken(CLAIM_NAME, accessToken);
@@ -62,13 +62,16 @@ export class LoginApiService implements LoginService {
const tokenId = data[AUTH_USER_ID];
if (!accessToken || !tokenId) {
throw new Error("Missing required authentication tokens");
throw new Error(
"[silentRefresh] Missing required authentication tokens",
);
}
tokenManager.setToken(CLAIM_NAME, accessToken);
tokenManager.setToken(AUTH_USER_ID, tokenId);
} catch (error) {
console.warn("Token has expired, logging out.");
tokenManager.clearAuthToken();
if (error instanceof AxiosError) {
throw new Error(
`Silent refresh failed: ${error.response?.data?.message || error.message}`,
@@ -40,12 +40,12 @@ export class TokenManager {
this.refreshTimer = setTimeout(() => {
callback().catch((error) => {
console.error("Silent refresh failed:", error);
this.clearAuth();
this.clearAuthToken();
});
}, expirationTime - 60000);
}
public clearAuth(): void {
public clearAuthToken(): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
@@ -0,0 +1,97 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { createContext, useContext, useState } from "react";
import { client } from "../api/client";
import { ENDPOINT } from "../config/api";
import { AUTH_USER_ID } from "../config/env";
import { getCookie } from "../lib/cookie";
import { AxiosError } from "axios";
import { LoginApi } from "@/entities/auth/login";
interface IUserInfo {
regDt: string;
mdfcnDt: string;
rgtr: string;
mdfr: string;
userUnqId: string;
userId: string;
userName: string;
email: string;
encryptedPassword: string;
refreshToken: string;
userStateCode: string;
lastLoginDate: string;
loginFailCount: number;
pswdHnt: string;
pswdAns: string;
mphn: string;
telno: string;
zip: string;
addr: string;
daddr: string;
laeSeCd: string;
ogdpSeCd: string;
aprvYn: string;
autzrId: string;
expryYmd: string;
lastPswdChgDt: string;
googleId: string;
kakaoId: string;
naverId: string;
isSocialUser: boolean;
hasPassword: boolean;
}
export interface AuthContextType {
userInfo: IUserInfo | null;
}
export const AuthContext = createContext<AuthContextType | null>(null);
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
const [userInfo, setUserInfo] = useState<IUserInfo | null>(null);
useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
queryFn: async () => {
try {
const response = await client.get<IUserInfo>(
`${ENDPOINT.USER_SERVICE.USERS}/${getCookie(AUTH_USER_ID)}`,
);
setUserInfo(response.data);
return response;
} catch (error) {
console.error(error);
if (error instanceof AxiosError) {
if (error.response?.status === 401) {
try {
await LoginApi.silentRefresh();
} catch (refreshError) {
console.error("Token refresh failed:", refreshError);
setUserInfo(null);
}
}
}
}
},
});
const authContextValue: AuthContextType = {
userInfo,
};
return (
<AuthContext.Provider value={authContextValue}>
{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;
};
@@ -1,7 +1,7 @@
"use client";
import { createContext, useContext } from "react";
import { getCookie, setCookie, removeCookie } from "../../lib/cookie";
import { getCookie, setCookie, removeCookie } from "../lib/cookie";
export interface CookieContextType {
getCookie: (name: string) => string | undefined;
+3 -6
View File
@@ -1,6 +1,3 @@
export {
CookieContext,
CookieProvider,
useCookie,
} from "./cookie/cookie-provider";
export { QueryProvider } from "./query/query-provider";
export { AuthProvider, useAuth } from "./auth-provider";
export { CookieProvider, useCookie } from "./cookie-provider";
export { QueryProvider } from "./query-provider";
+14 -2
View File
@@ -1,4 +1,4 @@
import axios from "axios";
import axios, { AxiosError } from "axios";
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import Cookies from "js-cookie";
import { AUTH_USER_ID, CLAIM_NAME } from "./env";
@@ -27,7 +27,19 @@ class Client {
const userId = Cookies.get(AUTH_USER_ID);
if (!token || !userId) {
throw new Error("Missing required authentication tokens");
throw new AxiosError(
"[Client] Missing required authentication tokens",
"UNAUTHORIZED",
config,
null,
{
data: { message: "Missing required authentication tokens" },
status: 401,
statusText: "Unauthorized",
headers: this.axiosInstance.defaults.headers,
config: config,
} as AxiosResponse
);
}
config.headers[CLAIM_NAME] = token;