Skip to content

Commit fef09e1

Browse files
authored
Merge pull request #120 from FC-InnerCircle-ICD2/feat/refresh-token
토큰 자동 갱신 로직 구현
2 parents 1dd9a3d + 708e92a commit fef09e1

File tree

2 files changed

+63
-9
lines changed

2 files changed

+63
-9
lines changed

src/lib/apiClient.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
2+
import { RefreshResponse } from '@/models/auth'
13
import ky from 'ky'
24

5+
// 리프레시 토큰용 별도 클라이언트 생성
6+
const refreshClient = ky.create({
7+
prefixUrl: process.env.NEXT_PUBLIC_API_URL,
8+
timeout: 10000,
9+
})
10+
11+
// 토큰 갱신 중임을 나타내는 Promise를 저장
12+
let refreshTokenPromise: Promise<RefreshResponse['data']> | null = null
13+
314
export const kyClient = ky.create({
415
prefixUrl: process.env.NEXT_PUBLIC_API_URL, // Base URL 설정
516
timeout: 10000, // 타임아웃 설정
@@ -13,17 +24,53 @@ export const kyClient = ky.create({
1324
},
1425
],
1526
afterResponse: [
16-
(_request, _options, response) => {
17-
// 예: 로깅 처리
18-
console.log(`Response: ${response.status} ${response.url}`)
27+
async (request, options, response) => {
28+
if (response.status === 511) {
29+
try {
30+
// 이미 진행 중인 토큰 갱신이 있다면 그 Promise를 재사용
31+
if (!refreshTokenPromise) {
32+
refreshTokenPromise = (async () => {
33+
const accessToken = localStorage.getItem('accessToken')
34+
const refreshToken = localStorage.getItem('refreshToken')
35+
36+
const response = await refreshClient
37+
.post('auth/refresh', {
38+
headers: {
39+
'Content-Type': 'application/json',
40+
},
41+
json: { accessToken, refreshToken },
42+
})
43+
.json<RefreshResponse>()
44+
45+
localStorage.setItem('accessToken', response.data.accessToken)
46+
localStorage.setItem('refreshToken', response.data.refreshToken)
47+
48+
return response.data
49+
})()
50+
}
51+
52+
// 토큰 갱신 완료 대기
53+
const newTokens = await refreshTokenPromise
54+
refreshTokenPromise = null // 갱신 완료 후 초기화
55+
56+
// 원래 요청을 새 토큰으로 재시도
57+
return ky(request, {
58+
...options,
59+
headers: {
60+
...options.headers,
61+
Authorization: newTokens.accessToken,
62+
},
63+
})
64+
} catch (error) {
65+
refreshTokenPromise = null // 에러 발생 시에도 초기화
66+
console.error('리프레시 토큰 갱신 실패:', error)
67+
localStorage.removeItem('accessToken')
68+
localStorage.removeItem('refreshToken')
69+
throw new Error('리프레시 토큰 만료')
70+
}
71+
}
1972
},
2073
],
21-
// beforeError: [
22-
// (error) => {
23-
// console.error(error.message)
24-
// return error
25-
// },
26-
// ],
2774
},
2875
})
2976

src/models/auth.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export interface LoginResponse {
1818
refreshTokenExpiresIn: string
1919
}
2020

21+
export interface RefreshResponse {
22+
data: {
23+
accessToken: string
24+
refreshToken: string
25+
}
26+
}
27+
2128
export interface Member {
2229
id: number
2330
signname: string

0 commit comments

Comments
 (0)