JWT 인증을 사용하는 웹 애플리케이션에서 액세스 토큰 만료는 사용자 경험에 중요한 영향을 미칩니다. 토큰이 만료되면 사용자가 다시 로그인해야 하는 번거로움이 생기죠. 이러한 문제를 해결하기 위해 리프레시 토큰을 활용한 자연스러운 갱신 메커니즘이 필요합니다. 이번 글에서는 Next.js 미들웨어를 활용해 액세스 토큰 자동 갱신 로직을 구현한 경험을 공유하고자 합니다.
미들웨어를 선택한 이유
토큰 갱신 로직을 구현할 수 있는 위치는 여러 곳이 있습니다.
- 컴포넌트 내부: 각 컴포넌트에서 토큰 만료 감지 시 갱신
- API 인터셉터: Axios 등의 라이브러리에서 401 응답 감지 시 갱신
- 미들웨어: 페이지 접근 전에 토큰 상태 확인 및 갱신
여러 위치 중에 미들웨어가 가장 깔끔한 솔루션이라고 판단되었습니다.
- 선제적 대응: 페이지 렌더링 전에 토큰 상태를 확인하므로 401 에러 자체를 방지
- 중앙화된 로직: 토큰처리 로직을 한 곳에서 처리
- 일관된 사용자 경험: 브라우저 새로고침이나 탭 전환 후에도 자동 갱신
구현 방식은
import { ResponseCookies } from 'next/dist/compiled/@edge-runtime/cookies';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { constants } from '@/constants';
// 인증이 필요한 보호된 경로 목록
const protectedPaths = ['/dashboard', '/profile'];
// 인증된 사용자에게 제한된 경로 (로그인 상태에서 접근 불가)
const authRestrictedPaths = ['/login', '/signup'];
export const middleware = async (request: NextRequest) => {
const accessToken = request.cookies.get('access_token')?.value;
const refreshToken = request.cookies.get('refresh_token')?.value;
const { pathname } = request.nextUrl;
// 로그인 상태인데 authRestrictedPaths에 접근하려는 경우 대시보드로 리디렉션
if (accessToken && authRestrictedPaths.includes(pathname)) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
// 리프레시토큰은 있는데 액세스토큰이 만료되어 사라졌을 경우 액세스토큰 재요청
if (
!accessToken &&
refreshToken &&
protectedPaths.some((path) => pathname === path || pathname.startsWith(path + '/'))
) {
try {
// 토큰 갱신 요청
const response = await fetch(`${constants.api.backendUrl}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Cookie: `refresh_token=${refreshToken}`,
},
});
if (!response.ok) {
return NextResponse.redirect(new URL('/login', request.url));
}
const responseCookies = new ResponseCookies(response.headers);
const nextResponse = NextResponse.next();
const accessToken = responseCookies.get('access_token');
if (accessToken) {
nextResponse.cookies.set('access_token', accessToken.value, {
httpOnly: accessToken.httpOnly,
sameSite: accessToken.sameSite,
path: accessToken.path,
secure: accessToken.secure,
});
}
return nextResponse;
} catch (error) {
console.error('Token refresh error:', error);
return NextResponse.redirect(new URL('/login', request.url));
}
}
// 비로그인 상태에서 보호된 경로에 접근하려는 경우 로그인 페이지로 리디렉션
if (
!accessToken &&
!refreshToken &&
protectedPaths.some((path) => pathname === path || pathname.startsWith(path + '/'))
) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
};
// 미들웨어가 적용될 경로 설정
export const config = {
matcher: [
// 정적 파일과 API 경로를 제외한 모든 경로에 적용
'/((?!_next/static|_next/image|favicon.ico|api/auth/refresh).*)',
],
};
미들웨어에서 토큰 갱신 구현 시 마주친 문제
Next.js 미들웨어는 Edge 런타임 환경에서 실행되며, 이는 일반적인 브라우저 환경과는 다릅니다. 이로 인해 다음과 같은 문제가 발생했습니다.
- Set-Cookie 응답 헤더를 자동으로 처리할 수 없음: 미들웨어는 브라우저가 아닌 서버 환경에서 실행되기 때문에 credentials: 'include' 설정이 예상대로 작동하지 않습니다.
- 쿠키 자동 전송 불가: 브라우저처럼 쿠키를 자동으로 요청에 포함시키는 메커니즘이 없습니다.
const response = await fetch(`${constants.api.backendUrl}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Cookie: `refresh_token=${refreshToken}`,
},
});
headers 안에 직접 refreshToken값을 담아서 요청보내야 한다.
요청이 성공할 경우에는 응답안에 headers 안에 cookie들을 볼 수 있는
const responseCookies = new ResponseCookies(response.headers);
const nextResponse = NextResponse.next();
const accessToken = responseCookies.get('access_token');
if (accessToken) {
nextResponse.cookies.set('access_token', accessToken.value, {
httpOnly: accessToken.httpOnly,
sameSite: accessToken.sameSite,
path: accessToken.path,
secure: accessToken.secure,
});
ResponseCookies로 직접 쿠키들을 꺼내고 쿠키의 설정 값들도 직접 오버라이딩 해줘야한다.
마치며
아직 Edge 환경에 대한 공부가 부족하여 이 미들웨어가 실제 배포 환경에서도 예상대로 작동할지 확신하지 못합니다. 추후 배포 과정에서 문제가 발생한다면 계속해서 수정하고 그 경험도, 또한 공유하도록 하겠습니다.
혹시 글에서 설명한 방식에 오류가 있거나 더 효율적인 방법을 알고 계시다면 댓글로 알려주시면 정말 감사하겠습니다.
참조
Next.js Middleware.ts에서 Set-Cookie가 즉시 적용되지 않는 문제: Route Handlers와 Middleware.ts 간 쿠키 동기
미들웨어에서 특정 엔드포인트 또는 라우트 핸들러의 응답 쿠키가 즉시 반영되지 않는 동기화 문제 해결 방법
velog.io
https://ianlog.me/blog/2024/server-component-cookie
Nextjs 앱라우터 서버컴포넌트에서 쿠키 세팅이 안되는 이유
next.js 서버 컴포넌트에서 쿠키 세팅시 오류가 나는 이유와 해결 방법에 대해 설명합니다. ( Error: Cookies can only be modified in a Server Action or Route Handler )
ianlog.me
https://nextjs.org/docs/app/building-your-application/routing/middleware
Routing: Middleware | Next.js
Learn how to use Middleware to run code before a request is completed.
nextjs.org
'nextjs' 카테고리의 다른 글
[NEXT.JS] Warning : Props 'className' did not match와 suppressHydarationWarning (1) | 2023.07.11 |
---|---|
[nextjs] Link 새창띄우기 (0) | 2023.04.09 |
build하고나서 Image 가 안나왔던 이유 nextjs Image error (0) | 2022.09.23 |
폴더명이 "ud"로 시작하면 생기는 에러 ? create-next-app (0) | 2022.09.05 |