Javascript

Next.js 14에서 JWT를 안전하게 관리하는 방법: Express 백엔드와의 통합

드리프트2 2024. 9. 19. 09:11

Next.js 14에서 JWT를 안전하게 관리하는 방법: Express 백엔드와의 통합

Next.js 14 프로젝트에서 JWT를 어떻게 관리하는 것이 가장 좋은 방법일지 고민이 많으실 텐데요.

 

특히 App Router를 사용하면서, 서버 컴포넌트는 Local Storage에 접근할 수 없기 때문에 JWT를 안전하게 다루는 방법에 대해 혼란스러울 수 있습니다.

 

오늘은 이 문제를 해결하기 위한 다양한 방법을 살펴보고, Express 백엔드와 어떻게 통합할 수 있는지 알아보겠습니다.


1. HTTPOnly 쿠키를 사용하자

가장 많이 추천되는 방법은 HTTPOnly 쿠키를 사용하는 것입니다.

 

HTTPOnly 쿠키는 클라이언트에서 JavaScript로 접근할 수 없기 때문에, 보안적으로 안전한 선택입니다.

 

특히 JWT와 같은 민감한 정보를 저장할 때 매우 유용하죠.

 

쿠키 설정 시, 아래와 같은 속성을 추가하면 보안을 더욱 강화할 수 있습니다:

  • httpOnly: 쿠키를 클라이언트의 JavaScript에서 접근하지 못하게 설정.
  • secure: HTTPS 연결에서만 쿠키를 전송.
  • sameSite: 크로스 사이트 요청에 대한 쿠키 전송 방지.

다음은 Express에서 JWTHTTPOnly 쿠키로 설정하는 예제입니다.

// Express에서 JWT를 HttpOnly 쿠키로 설정하는 예시
app.post('/login', (req, res) => {
    const token = generateJWT(req.user); // JWT 토큰 생성
    res.cookie('token', token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production', // 프로덕션 환경에서만 secure 옵션 활성화
        sameSite: 'strict', // CSRF 방지를 위한 쿠키 설정
    });
    res.json({ message: '로그인 성공' });
});

 

위 코드에서는 generateJWT() 함수를 통해 JWT를 생성하고, HTTPOnlysecure 옵션이 활성화된 쿠키로 설정합니다.

 

이렇게 설정된 쿠키는 클라이언트의 JavaScript가 접근할 수 없기 때문에, 보안성이 강화됩니다.


2. 클라이언트에서 JWT를 직접 사용하지 말자

많은 개발자들이 JWT를 클라이언트에서 직접 사용하고 싶어 하지만, 이는 추천되지 않는 방법입니다.

 

JWT는 주로 서버 간의 통신에서 사용되며, 클라이언트가 이를 직접적으로 접근할 필요는 없습니다.

 

클라이언트 컴포넌트에서 JWT를 필요로 하는 대신, 서버 컴포넌트에서 데이터를 처리한 후 필요한 정보만 클라이언트로 전달하는 것이 더 안전한 방법입니다.

 

예를 들어, 서버에서 사용자 인증 정보를 처리한 후, 클라이언트로 사용자 프로필을 넘겨줄 수 있습니다.

// 서버 컴포넌트에서 사용자 데이터를 클라이언트 컴포넌트로 전달하는 예시
import { cookies } from 'next/headers';

export default function Profile() {
    const cookieStore = cookies();
    const token = cookieStore.get('token'); // 쿠키에서 JWT 가져오기

    // 서버에서 사용자 데이터를 가져오는 로직
    const userData = fetchUserData(token);

    return (
        <div>
            <h1>안녕하세요, {userData.name}님!</h1>
            <p>이메일: {userData.email}</p>
        </div>
    );
}

 

이 코드에서는 Next.js의 cookies() 함수를 사용해 서버 컴포넌트에서 JWT를 가져오고, 그 토큰을 이용해 사용자 데이터를 서버에서 처리한 후 클라이언트로 넘겨줍니다.

 

이렇게 하면 클라이언트에서 직접 JWT를 다루지 않아도 되고, 보안성이 확보됩니다.


3. 서버 액션을 통한 쿠키 설정

Next.js 14에서는 서버 액션(Server Actions) 기능을 통해 쿠키를 쉽게 설정하고 관리할 수 있습니다.

 

로그인 시 서버 액션을 사용해 JWT를 쿠키에 저장하고, 이후 요청에서 해당 쿠키를 참조해 인증을 처리할 수 있습니다.

// 서버 액션을 사용한 JWT 설정 예시
'use server';

// 로그인 처리 함수 (서버 액션)
import { cookies } from 'next/headers';

export async function loginAction(formData) {
    const { username, password } = formData;

    // 로그인 로직 (Express 서버로 요청 보내기)
    const token = await authenticateUser(username, password); 

    // JWT를 HttpOnly 쿠키로 설정
    cookies().set('token', token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
    });

    return { success: true };
}

 

이 예제에서는 서버 액션을 통해 로그인 요청을 처리하고, 성공 시 JWTHTTPOnly 쿠키로 설정합니다.

 

이 방식은 클라이언트에서 JWT를 직접 다루지 않고, 서버에서 안전하게 관리할 수 있도록 해줍니다.


4. 리프레시 토큰과 액세스 토큰 활용하기

더 안전한 인증 방식을 원한다면, 리프레시 토큰액세스 토큰을 함께 사용하는 방법이 있습니다.

 

이 방법은 액세스 토큰이 만료되었을 때 리프레시 토큰을 통해 새로운 액세스 토큰을 발급받고, 세션을 유지할 수 있도록 도와줍니다.

// 리프레시 토큰과 액세스 토큰을 사용하는 예시
app.post('/refresh', (req, res) => {
    const refreshToken = req.cookies.refreshToken;
    if (!refreshToken) {
        return res.status(401).json({ message: '토큰이 없습니다.' });
    }

    // 리프레시 토큰을 확인하고 새로운 액세스 토큰 발급
    const newAccessToken = verifyAndGenerateNewAccessToken(refreshToken);
    res.json({ accessToken: newAccessToken });
});

 

위 코드는 리프레시 토큰을 HttpOnly 쿠키로 저장하고, 액세스 토큰이 만료되었을 때 새로운 액세스 토큰을 발급받는 예시입니다.

 

이렇게 하면 사용자가 로그아웃하지 않고도 세션을 안전하게 유지할 수 있습니다.


5. NextAuth.js와 같은 인증 라이브러리 사용하기

NextAuth.js와 같은 인증 라이브러리를 사용하면 JWT와 세션 관리가 훨씬 간편해집니다.

 

이 라이브러리는 기본적으로 JWT, 세션 관리, 쿠키 설정 등을 자동으로 처리해주기 때문에, 복잡한 인증 로직을 간소화할 수 있습니다.

// NextAuth.js 설정 예시
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';

export default NextAuth({
    providers: [
        Providers.Credentials({
            async authorize(credentials) {
                const user = await authenticateUser(credentials.username, credentials.password);
                if (user) {
                    return user;
                } else {
                    throw new Error('인증 실패');
                }
            },
        }),
    ],
    jwt: {
        encryption: true,
    },
    session: {
        jwt: true,
    },
    callbacks: {
        async jwt(token, user) {
            if (user) {
                token.id = user.id;
            }
            return token;
        },
        async session(session, token) {
            session.user.id = token.id;
            return session;
        },
    },
});

 

NextAuth.js는 다양한 인증 방식을 지원하며, JWT세션 관리를 자동으로 처리해줍니다.

 

이를 통해 복잡한 인증 로직을 간소화할 수 있으며, 더 안전한 방식으로 JWT를 다룰 수 있습니다.


결론: JWT를 안전하게 다루는 방법

Next.js 14에서 JWT를 관리하는 데 있어 가장 중요한 것은 보안입니다.

 

HTTPOnly 쿠키에 JWT를 저장하고, 클라이언트에서 직접 접근하지 않도록 하는 것이 기본적인 보안 원칙입니다.

 

또한, 서버 액션을 사용해 쿠키를 관리하거나, 리프레시 토큰액세스 토큰을 함께 사용해 보안을 강화할 수 있습니다.

 

NextAuth.js와 같은 라이브러리를 활용하면 인증 로직을 간소화할 수 있으며, 더 나아가 애플리케이션의 전반적인 보안성을 높일 수 있습니다.

 

무엇보다 중요한 것은, 프로젝트의 요구 사항에 맞는 인증 방식을 선택하는 것입니다.