서버 컴포넌트에서 데이터 패칭을 위한 AxiosInstance 설정하기

silver·2025년 5월 14일

프론트엔드

목록 보기
2/5
post-thumbnail

배경

나는 CSRF 공격 방지를 위해 CSRF 토큰 기능을 구현했다. 내가 클라이언트 단에서 사용하는 CSRF 토큰 기능은 다음과 같다.

백엔드 미들웨어

Request의 쿠키에서 CSRF 토큰을 가져온다.
=> Request의 헤더에서 x-csrf-token을 가져온다.
=> 둘이 일치하지 않거나 둘 중 하나가 없으면 403으로 응답한다.

프론트엔드 서버

CSRF 토큰을 발급해서 쿠키에 담는 /csrf-token API Route 구현
(클라이언트에서 쿠키를 사용하기 위해 httpOnly는 false로 하는 대신 sameSite를 Strict로 설정)

프론트엔드 클라이언트

Axios request interceptor를 통해 매 요청마다 쿠키에 CSRF 토큰이 있는지 확인
=> 없을 경우 /csrf-token API Route 호출
=> 호출 이후 또는 쿠키가 있을 경우 쿠키에서 CSRF token 가져온 다음 x-csrf-token 헤더에 CSRF토큰 값을 추가

문제

서버 컴포넌트에서 데이터를 패칭할 때도 CSRF 토큰을 포함시키지 않는다면 미들웨어를 통과할 수 없다. 그동안은 AxiosInstance를 쓰지 않고 fetch를 썼었는데 매번 요청 때마다 CSRF 토큰을 가져오고 추가하는 작업을 해야한다는 점에서 비효율적이었다.

그래서 서버 컴포넌트에서 데이터를 패칭할 때도 AxiosInstance를 쓰기로 했다.
한가지 문제점이 있다. 클라이언트에서 사용하는 AxiosInstance의 경우 사용자의 브라우저에 있는 메모리에 생성되기 때문에 한 사용자가 하나의 AxiosInstance를 사용하게 된다.

하지만 서버에서 사용할 땐 여러 사용자가 동일한 서버를 공유하기 때문에 하나의 AxiosInstance에 대해 여러 사용자가 사용하게 된다. 그러면 요청 간에 쿠키나 헤더가 충돌할 수 있어서 문제가 발생할 수 있다.

이를 해결하기 위해 요청 때마다 AxiosInstance를 생성하도록 구현했다. 이렇게 했을 때 성능적으로 문제가 없을지 궁금했다. GPT에 질문했을 때 이렇게 답변했다.

구현

import crypto from 'crypto';
import axios from 'axios';
import { cookies } from 'next/headers';

export const createServerAxiosInstance = () => {
  const instance = axios.create({
    baseURL: process.env.NEXT_PUBLIC_API_URL,
    timeout: 5000,
    headers: {
      'Content-Type': 'application/json',
    },
  });

  instance.interceptors.request.use((config) => {
    const cookieStore = cookies();
    const allCookies = cookieStore.toString();

    const csrfToken = crypto.randomBytes(32).toString('hex');

    const cleanedCookies = allCookies
      .split(';')
      .map((cookie) => cookie.trim())
      .filter((cookie) => !cookie.startsWith('csrfToken='))
      .join('; ');

    const cookieWithCsrf = cleanedCookies
      ? `${cleanedCookies}; csrfToken=${csrfToken}`
      : `csrfToken=${csrfToken}`;

    config.headers['Cookie'] = cookieWithCsrf;
    config.headers['X-CSRF-Token'] = csrfToken;

    return config;
  });

  return instance;
};

기존의 AxiosInstance와 구현하는 코드는 비슷하다. 다만 CSRF 토큰이 만료된 경우가 조금 복잡했다. CSRF토큰을 API Route를 통해 쿠키에 담고 있는데 서버에서 서버로 요청할 경우 응답은 오지만 쿠키를 자동으로 저장하지 않기 때문이다.

응답에서 쿠키를 직접 꺼내 파싱해서 쓰는 방법도 있지만, 그보단 CSRF토큰을 새로 생성하는 것이 더 간편했다. 그래서 쿠키에 CSRF 토큰이 있거나 없거나 상관 없이 그냥 새로 생성한 다음 쿠키와 헤더에 붙이도록 설정했다.

이렇게 설정하니 서버 컴포넌트에서 데이터를 패칭할 때도 CSRF 미들웨어를 통과해서 데이터를 잘 불러오는 것을 확인했다.


import { createServerAxiosInstance } from '@/lib/axiosForServer';

export const getQuoteRequest = async () => {
  const instance = createServerAxiosInstance();
  try {
    const response = await instance.get('/quote-requests/latest');
    return response.data;
  } catch (e) {
    console.log(e);
    throw e;
  }
};

0개의 댓글