문제 상황
- 미들웨어에서 엑세스 토큰이 만료되었을 경우를 처리하기 위해 다음과 같은 로직을 추가했다.
- 엑세스 토큰 확인: 미들웨어가 요청의
accesstoken
쿠키를 확인하여 유효한지 검사한다.
- 재발급 처리: 엑세스 토큰이 만료된 경우, 리프레쉬 토큰을 이용해
/api/v1/auth/refresh
엔드포인트로 새로운 엑세스 토큰을 요청한다.
- 요청 이어가기: 새로 발급받은 엑세스 토큰을 설정하여 원래 요청을 이어서 처리하도록 한다.
- 그러나, 핸들러(
/api/v1/auth/refresh
)에서 Set-Cookie
를 통해 새로운 엑세스 토큰을 설정했음에도, 이 값을 미들웨어에서 바로 읽지 못하는 문제가 발생했다.
문제의 원인
요청-응답 타이밍 문제
- 핸들러(
/api/v1/auth/refresh
)는 브라우저에 "새로운 엑세스 토큰을 저장하라"는 명령(Set-Cookie
)을 응답으로 보낸다.
- 하지만, 브라우저가 이 명령을 처리하기 전에 미들웨어가 실행된다.
- 결과적으로, 브라우저는 쿠키를 저장하지 않았고, 미들웨어가 요청 쿠키에서 새로운 토큰을 찾을 수 없었다.
- 미들웨어는 요청이 서버로 도착할 때 실행되기 때문에 브라우저에 저장된 쿠키가 아닌, 핸들러의 응답 헤더에 포함된
Set-Cookie
를 수동으로 읽어야 했다.
cookies()
API의 한계
- Next.js의
cookies()
API는 요청 헤더에 포함된 쿠키만 읽는다.
- 핸들러 응답의
Set-Cookie
헤더를 읽을 수 있는 기능이 없어, 새로 설정된 쿠키를 바로 활용할 수 없다.
해결 방법
ResponseCookies
활용
- Next.js에서 제공하는
ResponseCookies
클래스를 사용하여 핸들러 응답의 Set-Cookie
헤더에서 쿠키를 추출한다.
- 이를 통해 새로 설정된 쿠키 값을 미들웨어에서 직접 확인하고 사용할 수 있다.
코드 구현
- 핸들러에서 쿠키를 설정한 후, 미들웨어가 핸들러 응답을 확인하여
Set-Cookie
헤더에서 쿠키 값을 추출한다.
ResponseCookies
클래스를 사용하여 응답 헤더의 쿠키를 파싱하고, 필요한 값을 가져온다.
코드 예시
핸들러 코드
const response = NextResponse.json({ message: 'Token refreshed' });
response.cookies.set('accesstoken', newAccessToken, {
httpOnly: true,
sameSite: 'strict',
path: '/',
});
return response;
미들웨어 코드
- Set-Cookie 헤더에서 새로 설정된 쿠키 추출
const response = await fetch(`${API_URL}/api/v1/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
cookie: `refreshtoken=${refreshtoken.value}`,
},
credentials: 'include',
});
if (!response.ok) {
return NextResponse.redirect(new URL('/login', request.url));
}
const resCookies = new ResponseCookies(response.headers);
const newAccessToken = resCookies.get('accesstoken')?.value;
if (newAccessToken) {
console.log('새로운 액세스 토큰이 유효합니다. 요청 처리를 계속 진행합니다.');
return NextResponse.next();
}
결론
- 문제는 요청-응답 사이클에서 발생하는 동작 타이밍의 차이와
cookies()
API의 한계에서 비롯되었다.
ResponseCookies
를 사용하여 핸들러 응답의 Set-Cookie
헤더에서 직접 쿠키를 추출함으로써 문제를 해결했다.
참고 문헌