Next.js 미들웨어에서 환경변수 사용하기

silver·2025년 4월 23일

프론트엔드

목록 보기
4/5
post-thumbnail

배경

나는 액세스 토큰 존재 여부를 통해 권한을 체크하는 미들웨어를 구현하고 있다. 만약 액세스 토큰이 있는데 로그인/회원가입 페이지로 이동한다면 다른 페이지로 이동시키고 액세스 토큰이 없는 상태로 로그인이 필요한 페이지로 이동한다면 로그인/회원가입 페이지로 이동시키는 것이다.

하지만 액세스 토큰 존재 여부만으로 권한을 체크한다면 누군가 액세스 토큰을 위조하여 쿠키에 넣었을 경우에 대응할 수 없다. 그래서 토큰에 대한 검증이 필요했고 이를 위해선 미들웨어에서도 백엔드에서 토큰을 생성할 때 사용한 JWT Secret을 사용해 액세스 토큰을 검증해야 했다.

문제

const ENCODED_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

try {
      const verified = await jwtVerify(accessToken, ENCODED_SECRET);
      console.log(verified);
    } catch (err) {
      console.log('err', err);
      const loginUrl = new URL('/sign-in', request.url);
      return NextResponse.redirect(loginUrl);
    }

.env 파일에 JWT Secret을 추가하고 process.env.JWT_SECRET을 통해 환경변수를 호출해서 JWT를 검증하도록 설정했다. 로컬환경에선 정상적으로 동작했지만 배포환경에선 제대로 동작하지 않았다. console.log를 통해 JWT Secret을 확인해봤지만 undefined로 뜨는 것으로 보아 JWT Secret을 읽어오지 못하는 것이 원인이었다.

원인

.env를 불러오기 위해선 node.js 환경에서 실행되어야 한다. 하지만 AWS Amplify나 Vercel 같은 배포환경에서 Next.js의 미들웨어는 Node.js가 아닌 V8 엔진 기반의 Edge Runtime에서 실행되는데 이 Edge Runtime 환경에선 .env 파일을 조회해서 process.env에 주입하지 않기 때문에 환경변수를 읽어오지 못한 것이다.

로컬 환경에선 정상적으로 작동한 이유 역시 로컬에선 Next.js 서버가 Node.js에서 실행되기 때문이다.

해결방법

기존에 process.env를 통해 환경변수를 조회하는 방식은 런타임에 해당 환경변수를 조회한다.


const useEnv =()=> {
	console.log(process.env.NEXT_PUBLIC_ANY_VALUE);
}

useEnv();

위의 코드처럼 환경변수를 조회해서 출력하는 함수가 있다면(예시일 뿐 실제로 이렇게 하면 환경변수로 입력하는 의미가 없어진다.) 해당 함수를 호출할 때 .env파일에 있는 NEXT_PUBLIC_ANY_VALUE 변수를 조회해서 출력하게 되는 것이다.

하지만 next.config 파일에서 env를 설정하면 빌드 타임에 해당 환경변수를 사용하는 모든 곳에 직접 값을 하드코딩하여 빌드하도록 설정할 수 있다.


const nextConfig ={
    env: {
    JWT_SECRET: process.env.JWT_SECRET,
  },
}

이렇게 하면 빌드된 결과는 아래 코드처럼 실제 값이 입력된 코드가 된다.

// 빌드 전
const ENCODED_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);

// 빌드 후
const ENCODED_SECRET = new
TextEncoder().encode('실제 JWT_SECRET 값');

이렇게 빌드할 때 해당 환경변수를 하드코딩하여 빌드하게 되면 .env에서 환경변수를 불러올 수 없는 미들웨어에서도 해당 환경변수를 정상적으로 사용할 수 있게 된다.

보안문제?

이렇게 JWT Secret 값이 하드코딩되면 보안상의 문제가 있진 않을까? 의문이 들 수 있다.
보안상의 문제가 되는 경우는 프론트엔드 번들에 포함되면 브라우저에서 코드를 확인이 가능한 경우다. 이 경우 프론트엔드 번들에는 해당 환경변수가 포함되지 않기 때문에 문제가 되지 않는다.

중요 포인트

중요한 점은, 환경변수 앞에 NEXT_PUBLIC을 붙이지 않는 것이다. 붙이게 되면 해당 변수를 사용하지 않더라도 클라이언트 번들에 포함되어서 노출되기 때문이다.

const nextConfig ={
	env : {
 		NEXT_PUBLIC_JWT_SECRET : process.env.JWT_SECRET, // 절대 금지!!
	}
}

클라이언트 모듈에서 import 금지

NEXT_PUBLIC이 붙지 않은 환경변수라도 만약 해당 환경변수를 사용하는 코드를 아래와 같이 클라이언트 모듈에서 import할 경우 환경변수가 하드코딩 되어 클라이언트 번들에 포함되기 때문에 이 역시 주의해야할 부분이다.

// 서버에서 사용할 함수
import { jwtVerify } from 'jose';

export const verifyToken =(accessToken:string)=> {
  jwtVerify(accessToken,process.env.JWT_SECRET);
}

// 클라이언트 모듈
'use client'

import {verifyToken} from './verifyToken.ts';

0개의 댓글