Next.js 미들웨어는 특정 페이지로 이동하거나 API Route를 호출하는 것과 같이 클라이언트에서 next.js 서버로 요청할 때 중간에 실행되는 함수다.
나는 특정 페이지에 접근할 때 로그인 여부를 체크할 때 미들웨어를 활용했다.먼저 회원 인증 시스템은 로그인 시 쿠키에 JWT 토큰을 저장하는 방식으로 구현했다. 먼저 로그인이 되어 있다면 브라우저의 쿠키에 토큰이 담겨있는 상태이고 요청할 때 쿠키가 포함된다. 그러므로 로그인 상태를 확인하려면 요청에 담긴 쿠키를 가져와서 토큰 값을 확인하면 된다.
절차는 다음과 같다.

요청에 담긴 쿠키는 request.cookies를 통해 가져올 수 있다. 그리고 쿠키에 있는 accessToken을 먼저 검증한다. 이때 Next.js의 미들웨어는 엣지 런타임에서 실행되고 엣지 런타임은 node.js의 기능 사용이 제한되기 때문에 Jwt 검증 라이브러리로 jose를 사용해야 한다.
또 미들웨어에서 NEXT_PUBLIC이 아닌 환경변수를 사용하기 위해선 nextconfig에서 별도 설정이 필요하다. 해당 내용은 아래 글에 정리해 두었다.
토큰을 갱신할 땐 fetch 함수에 Cookie:`refreshToken=${refreshToken}` 를 입력해 headers에 쿠키를 설정해줘야 한다.
그리고 만약 갱신에 성공했다면 갱신 API 호출에 주어진 응답에서 headers.getSetCookies() 를 통해 쿠키를 가져오고 NextResponse.headers에 넣은 다음 response를 리턴하면 된다.
만약 토큰 갱신에 실패했다면 refreshToken도 만료됐을 수 있다. 이 때 쿠키에 아직 액세스토큰,리프레쉬토큰이 남아있다면 이를 삭제하고 로그인 페이지로 이동시킨다. const response = NextResponse.redirect(loginUrl)를 통해 응답에 리다이렉트하도록 설정하고 response.cookies.delete()를 통해 액세스 토큰과 리프레쉬 토큰을 제거하고 response를 리턴한다.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';
const refreshAccessToken = async(refreshToken:string)=> {
try{
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/auth/refresh-token`,
{
method: 'POST',
headers: {
Cookie: `refreshToken=${refreshToken}`,
},
},
)
return response;
} catch(error) {
console.error('액세스 토큰 갱신 실패', error);
return null;
}
}
export default async function middleware(request:NextRequest) {
const ENCODED_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);
const accessToken = request.cookies.get('accessToken')?.value;
const refreshToken = request.cookies.get('refreshToken')?.value;
const loginUrl = new URL('/sign-in',request.url);
if (!accessToken && !refreshToken) return NextResponse.redirect(loginUrl);
try {
await jwtVerify(accessToken,ENCODED_SECRET);
return NextResponse.next();
}catch(err) {
if (refreshToken) {
const refreshedTokenResponse = await refreshAccessToken(refreshToken);
if (!refreshedTokenResponse) {
const response = NextResponse.redirect(loginUrl);
response.cookies.delete('accessToken');
response.cookies.delete('refreshToken');
return response;
}
if (refreshedTokenResponse.ok) {
const response = NextResponse.next();
const setCookieHeader = refreshedTokenResponse.headers.getSetCookies();
setCoockieHeader.split(',').forEach((cookie)=> response.headers.append('Set-Cookie',cookie);
return response;
}
}
return NextResponse.redirect(loginUrl);
}
Next.js 미들웨어에 보안 취약점이 발견됐다. CVE-2025-29927
x-middleware-subrequest 헤더가 원인인데 이 헤더는 원래 미들웨어의 재귀 호출을 방지하기 위한 헤더이다. 하지만 해당 헤더를 외부에서 조작해 요청에 포함시키면 Next.js가 해당 요청을 NextResponse.redirect(),rewrite() 등을 통해 발생하는 내부 서브 요청으로 오인해 미들웨어를 실행하지 않게 된다.

크롬 익스텐션인 Modheader를 설치하고 여기에 x-middleware-subrequest 헤더에 src/middleware:를 5번 입력하고 미들웨어가 작동해야되는 페이지로 이동하게 되면 미들웨어가 작동하지 않는 것을 확인할 수 있다.
해당 문제는 버전이 업데이트되면서 x-middleware-subrequest 헤더가 클라이언트 요청에서 오면 무시하도록 수정되었다. 수정된 버전은 다음과 같다.
12.x : 12.3.5
13.x : 13.5.9
14.x : 14.2.25
15.x : 15.2.3
만약 Vercel이나 Netlify를 통해 배포하고 있거나 middleware를 사용하지 않고 있다면 문제가 되지 않는다.
하지만 만약 next start 명령어와 output: 'standalone' 설정을 사용하는 자체 호스팅 환경이라면 본인의 버전을 확인하고 반드시 업데이트해야 한다.
나는 AWS Amplify를 통해 배포하고 있는데 직접 확인해보니 해당 헤더를 포함시켰을 때 미들웨어가 작동하지 않았다. 이후 버전을 업데이트하고 새로 배포했고 헤더를 포함시켜도 미들웨어가 정상적으로 작동하는 것을 확인했다.