짧은 지식으로 인해 글의 신뢰성이 높지 않습니다. 정확한 내용은 공식문서를 참고해 주세요.
목차)
1. 서론
2. Nextjs 미들웨어 작동원리
3. 미들웨어에서 토큰 유효성 검증 (미들웨어 런타임에 따른 트러블 슈팅)
1. 서론
이 글은 Nextjs middleware에서 토큰 유효성 검증을 하며, 미들웨어에 대해 배운 내용을 공유한 글입니다.
미들웨어는 edge runtime에서 실행되고 따라서 jsonwebtoken라이브러리를 직접 사용할 수 없었다.
crypto등 일부 노드 모듈을 지원하지 않기 때문이다. 그런데 이 에러가 바로 로그로 찍히지 않고 엉뚱하게 a,b,c,,,기타 모듈이 설치되지 않았다는 에러 로그가 나와 문제 해결에 헤메었던 기억에 이 글을 작성한다.
얼마 전에 reddit에서 "Why We Moved out from nextjs"라는 뉘앙스의 글을 봤는데 그중에 하나가 버그리포트나 에러 안내가 불친절해서였다.
이번 글에서와 며칠 전 hydration mismatch에러도 겪으며 (나의 기본 지식의 부재로 인한 거겠지만) 에러 로그가 상냥하지 않다는 것에 공감을 하기도 했었다.
2. Next.js 미들웨어 작동원리
미들웨어는 request가 완료되기 전에 실행되는 코드이다. 요청이 들어올 때, 응답을 수정할 수 있다(재 작성, redirect, request나 response헤더 등을 수정).
미들웨어는 콘텐츠 캐시나 라우트 매치 이전에 실행된다.
나의 경우 로그인확인(protected route)으로 미들웨어에서 path를 확인해서 로그인하지 않았을 경우 로그인 페이지로 리다이렉트 하거나 토큰의 유효성을 검증하고 토큰 재발급을 하는 작업에 활용했다.
개인 느낌을 화살표로 나타내자면
request 시작 -> 어? request가 있네? 잠시만 미들웨어 먼저 실행할게 (인증로직, response헤더 수정, redirect 등) -> 미들웨어 실행 끝 -> request (라우트, api요청 등등 request 실행)
사실 미들웨어를 쓰면서 여러 이상한 실행흐름(쿠키동기화, 타이밍이슈)으로 트러블슈팅을 했는데,
response를 바꿔도 이미 요청된 request에 대한 직접적 조작은 되지 않는다는 점 등이 헷갈렸다. 아직도 잘 모르겠는 부분이 많다. 그중에서도 오늘 글은 미들웨어의 런타임에 대한 트러블슈팅에 대해 작성하였다.
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
3. 미들웨어에서 토큰 유효성 검증 (미들웨어 런타임에 따른 트러블 슈팅)
import { NextRequest, NextResponse } from "next/server";
import { ACCESS_TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME } from "./utils/constants";
import { validateAccessToken } from "./utils/validateAccessToken";
/**
* 미들웨어 함수
*
* @param {NextRequest} request - 클라이언트의 요청 객체
* @returns {NextResponse} - 다음 응답 객체
*/
export async function middleware(request: NextRequest) {
const url = new URL(request.url);
const refreshToken = request.cookies.get(REFRESH_TOKEN_COOKIE_NAME)?.value;
const accessToken = request.cookies.get(ACCESS_TOKEN_COOKIE_NAME)?.value;
// accessToken 유효성 검사
const { error } = await validateAccessToken(accessToken);
// accessToken이 유효하지 않을 경우 토큰 재발급 시도
if (error) {
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL}/api/auth/refresh`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Cookie: `${REFRESH_TOKEN_COOKIE_NAME}=${refreshToken}`,
},
}
);
if (response.ok) {
const data = await response.json();
const newAccessToken = data.accessToken; // 새 액세스 토큰 저장
// 새로운 access token을 쿠키에 설정
const responseWithNewToken = NextResponse.next();
responseWithNewToken.cookies.set(ACCESS_TOKEN_COOKIE_NAME, newAccessToken, {
httpOnly: true,
path: "/",
});
return responseWithNewToken; // 새로운 토큰을 가진 응답 반환
} else {
console.error("response 토큰 발급 실패");
return NextResponse.redirect(new URL("/signin", request.url)); // 토큰 발급 실패 시 로그인 페이지로 리디렉션
}
} catch (err) {
console.error("Error refreshing access token:", err);
return NextResponse.redirect(new URL("/signin", request.url)); // 에러 발생 시 로그인 페이지로 리디렉션
}
}
// 모든 조건을 만족하지 않을 경우 요청을 계속 진행
return NextResponse.next();
}
다른 부분은 생략하고 토큰검증 및 재발급 부분을 가져왔다.
1. accessToken이 유효한지 확인
2. accessToken이 유효하지 않을 경우 토큰 재발급
3. 토큰 재발급 성공 시 response의 쿠키를 재발급 토큰으로 변경
4. 토큰 재발급 실패 시 로그인페이지로 이동
등 의 조건이 있다. Next문서의 미들웨어 use case에도 설명된 대로 일반적인 인증 로직을 처리하고 있다.
1) 에러 분석
그런데 Invalid token: [Error: The edge runtime does not support Node.js 'crypto' module.
에러가 발생했다
구글링 한 결과 미들웨어는 엣지런타임이고, 엣지런타임에서는 crypto 등 몇몇 노드 모듈을 지원하지 않는다.
나는 토큰유효성 함수 안에서 jsonwebtoken라이브러리를 바로 실행하는데 이때 노드런타임(서버환경)에서는 작동하지만 edge런타임인 미들웨어에서는 jsonwebtoken을 지원하지 않아(정확히는 jwt를 쓸 때 사용하는 모듈을 지원하지 않아서) 발생한 문제였다.
2) 해결 방안
jsonwebtoken라이브러리 대신 엣지런타임도 지원하는 jose 라이브러리를 사용해 토큰의 유효성 검증
gpt가 알려주길 엣지 런타임 지원 외에도 다음 장점이 있다고 한다.
- 엣지 런타임 지원: jose는 엣지 환경에서 JWT를 생성하고 검증할 수 있도록 설계됨.
- 모듈 크기: jose는 경량화되어 있어 엣지 런타임에서 성능에 긍정적인 영향을 미침.
- API 간편성: jose는 JWT 관련 작업을 쉽게 처리할 수 있는 다양한 API를 제공함.
사용법은 매우 간단한데
jwt의 verify함수를 jose의 verify함수로 바꾸면 된다.
단, secretKey는 textEncoder로 엔코딩 해줘야 함 주의
import { jwtVerify } from "jose";
import { NextResponse } from "next/server";
// TextEncoder required by jose
const ACCESS_SECRET = new TextEncoder().encode(process.env.JWT_SECRET);
// Edge 함수 런타임에서도 실행하기 위해 jose로 jwtVerify
const isValidJWT = async (token: string): Promise<boolean> => {
try {
const secret = ACCESS_SECRET;
await jwtVerify(token, secret);
return true;
} catch (error) {
return false;
}
};
export const validateAccessToken = async (accessToken: string) => {
if (!accessToken || !(await isValidJWT(accessToken))) {
return {
error: NextResponse.json(
{ message: "Invalid or expired access token" },
{ status: 401 }
),
};
}
return { error: null };
};
https://www.npmjs.com/package/jose?activeTab=readme
jose
JWA, JWS, JWE, JWT, JWK, JWKS for Node.js, Browser, Cloudflare Workers, Deno, Bun, and other Web-interoperable runtimes. Latest version: 5.9.2, last published: 8 days ago. Start using jose in your project by running `npm i jose`. There are 1724 other proje
www.npmjs.com
'next.js' 카테고리의 다른 글
| Zod로 런타임에서 데이터 무결성을 확보한 경험 (0) | 2026.02.03 |
|---|---|
| Nextjs hydration mismatch error(하이드레이션) (0) | 2024.09.29 |
| API Route에서 jwt 토큰 유효성검증, next/headers cookies 함수 (0) | 2024.09.10 |
| NextAuth.js, Lucia auth, 토큰과 세션의 특징, 라이브러리 선택 시 유의할 점 (0) | 2024.09.05 |
| vercel 배포 느림 해결 (vercel region 지역 설정) (0) | 2024.08.27 |