App Router SSR JWT Cookie 관리 전략 (2) - Middleware

JT·2025년 4월 15일
post-thumbnail

이전 포스팅에서 App Router 환경에서 SSR 시 쿠키 전달 문제를 SSR 측 리디렉션 처리로 해결하고, 클라이언트가 토큰 재발급을 수행하도록 위임하는 전략을 다뤘습니다.
이번 글에서는 Next.js Middleware를 활용하여 SSR 전 JWT Cookie를 관리하는 방법에 대해 살펴보겠습니다.


1. Middleware ?

웹 어플리케이션에서 미들웨어는 클라이언트의 요청(Request)과 서버의 응답(Response) 사이에서 요청을 가공하거나, 검사하거나, 추가 처리를 해주는 중간 역할의 함수 또는 구성 요소입니다.

🧩 Next.js에서의 Middleware란?

Next.js의 Middleware라우팅이 일어나기 전에 실행되는 코드로, 요청(Request)을 조작하거나 조건에 따라 리다이렉트, 응답 처리 등을 미리 수행할 수 있는 기능입니다.

즉, 사용자가 어떤 페이지에 접근하기 전에, 요청을 가로채어 필요한 작업(예: 인증 체크, URL 리다이렉트, 응답 헤더 추가 등)을 할 수 있습니다.

Next.js의 Middleware를 활용하여 인증이 필요한 페이지 SSR 실행 전 WT 토큰 검사 및 재발급 처리를 미리 수행할 수 있습니다.


1️⃣ Middleware 파일 생성

Nextjs의 Middleware 파일은 최상단 root 폴더 하나만 생성할 수 있습니다. Next.js가 인식 가능하도록 파일이름은 반드시 middleware로 설정해야합니다.

my-nextjs-app/
├── app/
├── pages/
├── public/
├── styles/
├── middleware.ts  ✅ 꼭 여기에 위치해야 함
├── next.config.js
└── ...

2️⃣ Middleware 기본 구조

export function middleware() {
  //...
}
  • 반드시 middleware라는 이름으로 export 되어야 합니다.
  • NextRequest, NextResponse를 함께 사용합니다.

3️⃣ config.matcher 설정

config.matcher는 middleware에서 middleware가 실행될 페이지를 설정하는 역할을 합니다.
middleware는 config.matcher를 설정하지 않으면 기본적으로 모든 경로에 적용됩니다.

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
}

4️⃣ Middleware 코드

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { isValidToken } from "./src/lib/api/token";

export async function middleware(req: NextRequest) {
  // 응답 생성
  const response = NextResponse.next();
  // request 쿠키에서 accessToken 값 가져오기
  const accessToken = req.cookies.get("accessToken")?.value || "";
  // request 쿠키에서 refreshToken 값 가져오기
  const refreshToken = req.cookies.get("refreshToken")?.value || "";

  // accessToken 유효성 검사
  const tokenVaildation = await isValidToken(
    accessToken,
    process.env.NEXT_SECRET_ACCESS_TOKEN_KEY as string
  );

  // accessToken이 만료된 경우
  if (
    !tokenVaildation?.isValid &&
    tokenVaildation?.message.includes("claim timestamp check failed")
  ) {
    try {
     
      // 모든 쿠키 가져오기
      const cookieStore = cookies();
      const cookie = (await cookieStore);
        .getAll()
        .map(({ name, value }) => `${name}=${value}`)
        .join("; ");
      
       // 토큰 재발급 요청
      const refreshTokenRes = await fetch(
        `${req.nextUrl.origin}/api/auth/refresh-token`,
        { 
          method: "POST",
          headers: {
          	Cookie: cookie, // 쿠키 넣기
          }
          credentials: "include", 
        }
      );
      const data = await refreshTokenRes.json();
      
      // response headers 쿠키 설정
      response.cookies.set("accessToken", data.accessToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === "production",
        sameSite: "strict",
        path: "/",
      });
		
      // 에러 발생시 throw
      if (!refreshTokenRes.ok) {
        throw refreshTokenRes;
      }
    } catch (error) {
      if (error instanceof Response) {
        // refreshToken 만료 시 (로그인 만료 처리)
        if (error.status === 401) {
          // headers 쿠키 삭제
          const response = NextResponse.redirect(new URL("/", req.url));
          response.cookies.delete("accessToken");
          response.cookies.delete("refreshToken");
          return response;
        } else {
          throw error;
        }
      }
    }
    
    return response;
  }
}

// middleware를 실행할 페이지 설정
export const config = {
  matcher: ["/profile/:path*"],
};

정리

Next.js MiddlewareSSR이 실행되기 전에 먼저 실행되어, 쿠키 검사 및 토큰 재설정 등 인증 관련 작업을 미리 수행할 수 있습니다.

이를 통해 클라이언트에 노출되지 않고, 보다 안전하게 SSR 환경에서 인증을 제어할 수 있습니다. 토큰이 만료되었을 경우, 미들웨어가 자동으로 재발급 처리 또는 리다이렉트 처리를 수행하여 사용자 경험을 보호합니다.

profile
함께 개선하는 프론트엔드 개발자

0개의 댓글