getServerSideProps의 쿠키 문제 해결 방법

박상은·2022년 9월 3일
6

🧺 bleshop 🧺

목록 보기
5/10

next-auth, axios, next.js를 사용한 프로젝트입니다.

😕 사전 지식

next.jsnext-authgetServerSideProps()를 사용하면서 발생한 문제입니다.

next-authCredentials방식 ( idpassword를 이용한 인증 방식 )을 사용해서 구현했고, 서버측에서는 아래와 같이 getSession()을 사용해서 로그인한 유저의 데이터를 받아올 수 있습니다.

  • /api/user/me.ts
// 임시로 만든 api ( 나의 상세 정보를 요청한다고 가정 )
import { getSession } from "next-auth/react";

// type
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { method } = req;
  // "getSession({ req })"와 같은 형태로 요청하면 본인이 지정한 데이터를 받아올 수 있습니다.
  // 여기서 만약 "next-auth.session-token"의 이름을 가진 쿠키가 존재하지 않는다면 서버에서 비로그인 상태로 인식하게 됩니다.
  const session = await getSession({ req });

  if (!session || !session.user) return res.status(403).json({ message: "접근 권한이 없습니다." });

  const userIdx = session.user.idx;

  try {
	// ... 로그인한 유저의 상세 데이터를 찾는 코드 작성
  } catch (error) {
    console.error(error);

    return res.status(500).json({ message: "서버측 에러입니다. 잠시후에 다시 시도해주세요!" });
  }
}

getSession()이 로그인한 유저의 데이터를 가져오는 방법은 브라우저에서 보내주는 세션 쿠키를 이용해서 유저를 식별합니다.
하지만 세션 쿠키가 없다면 로그인하지 않은 유저라고 인식하게 됩니다.
이 부분을 먼저 이해하고 넘어가야 문제의 원인을 파악할 수 있습니다.

🤔 문제 원인

프론트에서 axios를 이용해서 백엔드로 특정 요청을 할때 쿠키를 같이 넘겨주는 방법은 withCredentialstrue로 세팅하면 됩니다.
그렇게 하면 프론트에서 자동으로 쿠키를 첨부에서 서버로 요청을 보내게 됩니다.

// "axios"의 인스턴스를 만들어서 공통 세팅을 하고 모든 요청을 인스턴스를 통해서 처리하도록 만들었습니다.
export const axiosInstance = axios.create({
  baseURL: process.env.NEXT_PUBLIC_FRONT_URL + "/api",
  withCredentials: true,
  timeout: 10000,
});

하지만 next.jsSSR을 위한 getServerSideProps()를 사용하는 경우에는 실행되는 위치가 프론트측(브라우저)이 아닌 서버측에서 실행되고 실행 결과를 이용한 완성된 페이지를 프론트로 넘겨주게 됩니다.

  • CSR 방식 실행 순서
    + 브라우저 axios 요청 --> 쿠키 첨부 --> 서버에 전달 --> 결과 반환 --> 받은 결괏값으로 브라우저 레이아웃 완성 --> 렌더링

  • SSR 방식 실행 순서
    + 서버에서 getServerSideProps() 실행 --> 결과로 페이지 완성 --> 완성된 페이지 반환 --> 브라우저에서 완성된 페이지 렌더링

getServerSideProps()는 서버에서 실행되기 때문에 브라우저에 저장되어 있는 쿠키를 알지 못합니다. 따라서 요청할 때도 쿠키를 같이 보내지 못하게 되기 때문에 서버측에서는 항상 비로그인상태로 인식하게 되는 문제를 찾았습니다.

🙂 해결 방법

자동적으로 쿠키를 첨부해주지 않으니 아래와 같이 직접 쿠키를 동봉해서 서버에 요청하는 방식으로 해결했습니다.

// type
import type { GetServerSideProps, GetServerSidePropsContext, NextPage } from "next";
// "Basket", "ApiGetBasketProductsResponse"은 그냥 특정 타입 ( 중요한 부분이 아니라 생략 )

type Props = {
  baskets: Basket[];
};

const BasketComponent: NextPage<Props> = ({ baskets }) => {
  return (
    <>
      // ... 생략
      // "baskets" 데이터를 이용한 화면 구성
    </>
  )
}

export default BasketComponent;

export const getServerSideProps: GetServerSideProps<Props> = async (context: GetServerSidePropsContext) => {
  /**
   * SSR 요청일 경우에는 서버에서 페이지를 완성한 후에 반환하는 방식임
   * 하지만 서버에서는 클라이언트(브라우저)에 대한 정보를 알 수 없으니 "cookie"(세션 쿠키)를 이용해서 유저를 식별함
   * 컴포넌트에 내부에서 하는 요청들은 "axios"의 "withCredentials" 옵션을 통해서 자동으로 쿠키를 넣어서 전달하지만
   * "getServerSideProps()"에서 전달하는 요청은 서버에서 서버로 보내는 요청이므로 쿠키의 존재를 몰라서 첨부하지 않고 요청을 보냄
   *
   * 따라서 직접적으로 쿠키를 넣어주는 행동을 하지 않으면 요청에 쿠키가 들어가지 않아서 서버가 어떤 유저인지 식별할 수 없기에 직접적으로 쿠키를 넣어주는 구문임
   */
  let { cookie } = context.req.headers;
  cookie = cookie ? cookie : "";
  axiosInstance.defaults.headers.Cookie = cookie;

  let baskets: Basket[] = [];

  try {
    // "axios"를 이용한 api 요청
    const { data } = await axiosInstance.get<ApiGetBasketProductsResponse>(`/products/basket`);
    baskets = data.baskets;
  } catch (error) {
    console.error("getServerSideProps basket/wish >> ", error);
  } finally {
    /**
     * "axios"에 등록한 특정 유저의 쿠키 제거
     * ( 브라우저에서의 요청이 아니라 서버에서의 요청이므로 다른 유저도 같은 서버를 사용하기에 쿠키가 공유되는 문제가 생김 )
     */
    axiosInstance.defaults.headers.Cookie = "";
  }

  return {
    props: {
      baskets,
    },
  };
};

만약 typescript를 사용한다면 axiosInstance.defaults.headers.Cookie에서 타입 에러가 발생하기 때문에 아래와 같은 파일을 생성하면 해결됩니다.

/**
 * "getServerSideProps"에서 로그인한 유저에 관한 요청을 하는 경우에 "cookie"를 넣어줘야 어떤 유저의 요청인지 알 수 있기 때문에 재정의
 */
import "axios";

declare module "axios" {
  export interface HeadersDefaults {
    Cookie?: string;
  }
}

1개의 댓글

comment-user-thumbnail
2023년 6월 5일

안녕하세요~! 도움되는 포스팅 정말 감사드립니다.
혹시 막히는부분이 있는데 답변 가능하시다면 .. 해주시면 감사하겠습니다. ㅠㅠ
getServerSideProps 부분에서 헤더에 쿠키 대신에
세션 안의 accessToken을 보내려면 어떻게 해야할까요..? const [session] = useSesseion() 이 안되서 애먹고 있습니다 ..

답글 달기