next-auth
,axios
,next.js
를 사용한 프로젝트입니다.
next.js
의 next-auth
와 getServerSideProps()
를 사용하면서 발생한 문제입니다.
next-auth
의 Credentials
방식 ( id
와 password
를 이용한 인증 방식 )을 사용해서 구현했고, 서버측에서는 아래와 같이 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
를 이용해서 백엔드로 특정 요청을 할때 쿠키를 같이 넘겨주는 방법은 withCredentials
을 true
로 세팅하면 됩니다.
그렇게 하면 프론트에서 자동으로 쿠키를 첨부에서 서버로 요청을 보내게 됩니다.
// "axios"의 인스턴스를 만들어서 공통 세팅을 하고 모든 요청을 인스턴스를 통해서 처리하도록 만들었습니다.
export const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_FRONT_URL + "/api",
withCredentials: true,
timeout: 10000,
});
하지만 next.js
의 SSR
을 위한 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;
}
}
안녕하세요~! 도움되는 포스팅 정말 감사드립니다.
혹시 막히는부분이 있는데 답변 가능하시다면 .. 해주시면 감사하겠습니다. ㅠㅠ
getServerSideProps 부분에서 헤더에 쿠키 대신에
세션 안의 accessToken을 보내려면 어떻게 해야할까요..? const [session] = useSesseion() 이 안되서 애먹고 있습니다 ..