Skip to content

Commit c15a0b4

Browse files
authored
Merge pull request #388 from WeGo-Together/chiyoung-refactor/auth
[Refactor] 인증 아키텍처 개선 - Axios 인스턴스 수정 및 refresh 주체 변경
2 parents 25e35ad + c9fe43d commit c15a0b4

6 files changed

Lines changed: 50 additions & 47 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import axios from 'axios';
22

3-
import { getAccessToken } from '@/lib/auth/token';
43
import { CommonErrorResponse } from '@/types/service/common';
54

65
import { createApiHelper } from '../lib/apiHelper';
76

8-
const authInstance = axios.create({
7+
const refreshInstance = axios.create({
98
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
109
timeout: 20000,
1110
withCredentials: true,
1211
});
1312

14-
authInstance.interceptors.request.use(async (config) => {
13+
refreshInstance.interceptors.request.use(async (config) => {
1514
const isServer = typeof window === 'undefined';
1615

1716
if (isServer) {
@@ -23,15 +22,10 @@ authInstance.interceptors.request.use(async (config) => {
2322
}
2423
}
2524

26-
const accessToken = await getAccessToken();
27-
if (accessToken && config.headers) {
28-
config.headers.Authorization = `Bearer ${accessToken}`;
29-
}
30-
3125
return config;
3226
});
3327

34-
authInstance.interceptors.response.use(
28+
refreshInstance.interceptors.response.use(
3529
(response) => {
3630
return response;
3731
},
@@ -40,4 +34,4 @@ authInstance.interceptors.response.use(
4034
},
4135
);
4236

43-
export const authAPI = createApiHelper(authInstance);
37+
export const refreshAPI = createApiHelper(refreshInstance);

src/api/service/auth-service/index.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { authAPI } from '@/api/core/auth';
1+
import { baseAPI } from '@/api/core/base';
2+
import { refreshAPI } from '@/api/core/refresh';
23
import { clearAccessToken, setAccessToken } from '@/lib/auth/token';
34
import {
45
GoogleOAuthExchangeRequest,
@@ -13,40 +14,45 @@ import {
1314
export const authServiceRemote = () => ({
1415
// 로그인
1516
login: async (payload: LoginRequest) => {
16-
const data = await authAPI.post<LoginResponse>('/api/v1/auth/login', payload);
17+
//prettier-ignore
18+
const data = await baseAPI.post<LoginResponse>('/api/v1/auth/login', payload, { withCredentials: true });
1719

1820
setAccessToken(data.accessToken, data.expiresIn);
1921
return data;
2022
},
2123

2224
// 회원가입
2325
signup: async (payload: SignupRequest) => {
24-
return authAPI.post<SignupResponse>(`/api/v1/auth/signup`, payload);
26+
return baseAPI.post<SignupResponse>(`/api/v1/auth/signup`, payload, { withCredentials: true });
2527
},
2628

2729
// 로그아웃
2830
logout: async () => {
29-
await authAPI.post<void>('/api/v1/auth/logout', null);
31+
await baseAPI.post<void>('/api/v1/auth/logout', null, { withCredentials: true });
32+
3033
clearAccessToken();
3134
},
3235

3336
// 액세스 토큰 재발급
3437
refresh: async () => {
3538
//prettier-ignore
36-
const data = await authAPI.post<RefreshResponse>('/api/v1/auth/refresh', {});
39+
const data = await refreshAPI.post<RefreshResponse>('/api/v1/auth/refresh', null, { withCredentials: true });
40+
3741
setAccessToken(data.accessToken, data.expiresIn);
3842
return data;
3943
},
4044

4145
// 회원 탈퇴
4246
withdraw: async () => {
43-
await authAPI.delete<void>('/api/v1/auth/withdraw');
47+
await baseAPI.delete<void>('/api/v1/auth/withdraw', { withCredentials: true });
48+
4449
clearAccessToken();
4550
},
4651

4752
// 구글 OAuth 코드 교환
4853
exchangeGoogleCode: async (payload: GoogleOAuthExchangeRequest) => {
49-
const data = await authAPI.post<GoogleOAuthExchangeResponse>('/api/v1/auth/google', payload);
54+
//prettier-ignore
55+
const data = await baseAPI.post<GoogleOAuthExchangeResponse>('/api/v1/auth/google', payload, { withCredentials: true });
5056

5157
setAccessToken(data.accessToken, data.expiresIn);
5258
return data;

src/mock/service/auth/auth-utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const createMockTokens = () => ({
1313
accessToken: 'mock-access-token',
1414
tokenType: 'Bearer' as const,
1515
expiresIn: 3600,
16+
expiresAt: '2026-02-21T11:05:19.595700269',
1617
});
1718

1819
export const createLoginResponse = (email: string, password: string): LoginResponse => {

src/providers/provider-auth/index.tsx

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
import React, { createContext, SetStateAction, useContext, useEffect, useState } from 'react';
2-
3-
import Cookies from 'js-cookie';
4-
5-
import { API } from '@/api';
1+
import React, { createContext, SetStateAction, useContext, useState } from 'react';
62

73
interface AuthContextType {
84
isAuthenticated: boolean;
@@ -25,25 +21,6 @@ interface Props {
2521
export const AuthProvider = ({ children, hasRefreshToken }: Props) => {
2622
const [isAuthenticated, setIsAuthenticated] = useState(hasRefreshToken);
2723

28-
// 초기값 설정
29-
// 페이지가 새로고침 될 때 accessToken이 없으면 refresh 시도, state update 실행
30-
useEffect(() => {
31-
const updateAuthenticated = async () => {
32-
const hasAccessToken = !!Cookies.get('accessToken');
33-
if (!hasAccessToken && hasRefreshToken) {
34-
try {
35-
await API.authService.refresh();
36-
setIsAuthenticated(true);
37-
} catch {
38-
setIsAuthenticated(false);
39-
}
40-
} else if (hasAccessToken) {
41-
setIsAuthenticated(true);
42-
}
43-
};
44-
updateAuthenticated();
45-
}, [hasRefreshToken]);
46-
4724
return (
4825
<AuthContext.Provider value={{ isAuthenticated, setIsAuthenticated }}>
4926
{children}

src/proxy.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { NextRequest, NextResponse } from 'next/server';
22

3-
export const proxy = (request: NextRequest) => {
3+
import { API } from './api';
4+
5+
export const proxy = async (request: NextRequest) => {
46
const accessToken = request.cookies.get('accessToken');
57
const refreshToken = request.cookies.get('refreshToken');
8+
let hasValidToken = !!accessToken;
69

710
const protectedPaths = ['/mypage', '/create-group', '/message', '/schedule', '/notification'];
811
const isProtected = protectedPaths.some((path) => request.nextUrl.pathname.startsWith(path));
@@ -11,24 +14,45 @@ export const proxy = (request: NextRequest) => {
1114
const isPublic = publicPaths.some((path) => request.nextUrl.pathname.startsWith(path));
1215

1316
// 인증된 사용자가 public 페이지 접근 시 홈으로
14-
if (isPublic && (accessToken || refreshToken)) {
17+
// refresh 중복 실행을 방지하기 위해 최상단으로 이동
18+
if (isPublic && refreshToken) {
1519
return NextResponse.redirect(new URL('/', request.url));
1620
}
1721

22+
// 일반 응답 생성
23+
const response = NextResponse.next();
24+
25+
// accessToken이 없으면 refresh 실행하여 일반 응답에 set cookie 설정
26+
if (!accessToken && refreshToken) {
27+
try {
28+
const res = await API.authService.refresh();
29+
const data = res;
30+
hasValidToken = true;
31+
response.cookies.set('accessToken', data.accessToken, {
32+
httpOnly: false,
33+
maxAge: data.expiresIn,
34+
domain: 'wego.monster',
35+
secure: process.env.NODE_ENV === 'production',
36+
});
37+
} catch {
38+
hasValidToken = false;
39+
}
40+
}
41+
1842
// 보호되지 않은 경로는 그냥 통과
1943
if (!isProtected) {
20-
return NextResponse.next();
44+
return response;
2145
}
2246

23-
// 둘 다 없으면 로그인 페이지로 redirect
24-
if (!accessToken && !refreshToken) {
47+
// accessToken 없으면 login redirect
48+
if (!hasValidToken) {
2549
const loginUrl = new URL('/login', request.url);
2650
loginUrl.searchParams.set('error', 'unauthorized');
2751
loginUrl.searchParams.set('path', request.nextUrl.pathname);
2852
return NextResponse.redirect(loginUrl);
2953
}
3054

31-
return NextResponse.next();
55+
return response;
3256
};
3357

3458
export const config = {

src/types/service/auth.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface LoginResponse {
2020
accessToken: string;
2121
tokenType: 'Bearer';
2222
expiresIn: number;
23+
expiresAt: string;
2324
user: {
2425
userId: number;
2526
email: string;

0 commit comments

Comments
 (0)