[NextJS] 13버전에서 cookie에 저장된 ‘accessToken’을 가져오려는데 undefined가 찍힌다

선영·2023년 7월 31일
13

📚 Library

목록 보기
11/14
post-thumbnail

🧨 문제


브랜드메뉴, 햄버거메뉴에서 사용되는 관심브랜드 기능을 구현하는 과정에서 로그인한 사용자만 관심브랜드 기능을 이용할 수 있다. 때문에 api요청시 아래와 같이 headers‘Authorization’값으로 accessToken값을 전달줘야 했다.

...

const product = {
    getInterestProduct: (queryString, accessToken) => {
        if (accessToken != undefined) {
            aaApi.defaults.headers.common['Authorization'] = 'Bearer ' + accessToken
        }
        return aaApi.get(...)
    },
}

export default product

그래서 관심브랜드의 하트토글 버튼을 사용자가 클릭했을때 상태객체의 accessTokennull이면, 로그인페이지로 리다이렉트하도록 했다.

// /hooks/useClickHeartToggle.ts
import {useAppSelector} from '../app/redux/hooks';
import {Brand} from '../types/brandTypes';

export default function useClickHeartToggle() {
    const {accessToken} = useAppSelector((state) => state.user);

    // 하트버튼 눌렀을때 관심브랜드 토글기능
    const handleClickHeart = (brand: Brand) => {
        if (!accessToken) {
            alert('로그인이 필요한 기능입니다.');
            window.location.href = url
        } else {
            console.log('brand', brand);
        }
    };

    return {
        handleClickHeart,
    };
}

그런데 로그인하고 다시 검색페이지로 돌아와서 쿠키에서 ‘accessToken’값을 얻어보려고 해도 undefined만 찍혔다. 웹스토리지엔 ‘accessToken’값이 잘 들어와 있는데도 말이다.

🤔 원인 및 시도


공식문서에서 설명하는 것처럼 cookies메서드를 사용하면, 서버 컴포넌트로부터 http 수신 요청 쿠키를 읽어들일 수 있다고 한다. 만약 이름을 찾을 수 없다면 undefined를 반환한다. 그러나 undefined가 찍혔다.

    // server-component
    import {cookies} from 'next/headers';
    
    import BrandMenu from '../../components/brands/brandMenu';
    
    // 페이지는 경로 세그먼트에 고유한 UI다.
    export default function BrandsPage() {
        const cookieStore = cookies();
        const accessToken = cookieStore.get('accessToken');
        // toDo 로그인을 해도 undefined가 찍힌다.
        console.log('accessToken', accessToken);
    
        return <BrandMenu />;
    }

아무래도 도메인이 달라서 인 것 같다..?
‘use client’키워드를 쓰는 클라이언트 컴포넌트에 아래와 같이 로직을 작성해주면, 해당 컴포넌트 생명주기가 시작될 때 setCookie의 세번째 인자의 프로퍼티 중 domain을 생략해주면 동일한 domain으로 쿠키가 저장되어 콘솔값이 잘 찍히지만,

아래와 같이 특정 도메인을 지정하여 웹스토리지에 저장하면 콘솔값이 아무것도 찍히지 않는다.

'use client';

import {useCookies} from 'react-cookie';

export default function Header() {
    const [cookies, setCookie, removeCookie] = useCookies(['accessToken']);

    const setCookieHandler = () => {
        setCookie('accessToken', 'dd', {
            domain: './blabla.com',
            path: '/',
        });
    };

    useEffect(() => {
        setCookieHandler();
        console.log('Cookies: ', cookies);
    }, [cookies]);

    return (
       ...
    );
}

근데 기존코드에서는 됐잖아? 그래서 기존 코드의 로직을 그대로 가져오려고했다. 그런데 ”getServerSideProps” is not supported in app/. 에러가 났다. 공식문서를 확인하니, pages router에서만 된단다. (즉, pages디렉터리에서만 된다는 뜻인 것 같음)

===Quoted===

Old Methods

Previous Next.js data fetching methods such as getServerSidePropsgetStaticProps, and getInitialProps are not supported in the new App Router. However, you can still use them in the Pages Router.

===End===

👍 해결


그래서 .blabla도메인이 일치하는 test.blabla 로 접속하니까 쿠키값이 문제없이 console.log로 출력되는 것을 확인할 수 있었다! 결국 예상한대로 도메인이 달라서, 즉 localhostblabla.com도메인이 달라서 생기는 문제였다.

근데 왜일까? http쿠키(웹 쿠키, 브라우저 쿠키)는 서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각이다. 즉, 쿠키는 두 요청이 동일한 브라우저에서 들어왔는지 아닌지를 판단할 때 주로 사용한다. 쿠키를 받는 과정은 아래와 같다.

1/ (클라이언트) ==http요청==> (서버a) : 서버가 클라이언트의 http요청을 수신

2/ (클라이언트) <==응답 + Set-Cookie헤더== (서버a)

  • 서버가 클라이언트의 요청에 대하여 응답
  • 아래 서버 헤더는 클라이언트에게 쿠키를 저장하라고 전달된다.
Set-Cookie: <cookie-name>=<cookie-value>

HTTP/1.0 200 OK
Content-type: text/html
Set-Cookie: yummy_cookie=choco
Set-Cookie: tasty_cookie=strawberry

[page content]

3/ (클라이언트) ==Cookie헤더==> (서버a)

  • 이 후 같은 서버에 대한 요청은 브라우저에 저장된 쿠키를 Cookie http헤더안에 포함하여 전송한다.
GET /sample_page.html HTTP/1.1
Host: www.example.org
Cookie: yummy_cookie=choco; tasty_cookie=strawberry
  • 여기엔 만료일 또는 지속시간도 명시 가능하다. 만료된 쿠키는 더이상 보내지지 않는다.
  • 특정 도메인 혹은 경로 제한을 설정할 수 있으며 이는 쿠키가 보내지는 것을 제한할 수 있다.

여기까지 정리한 후에 다시 위의 이슈를 되짚어 보자면, 처음에 localhost도메인에서 전역 상태 관리 객체를 확인해서 accessToken값이 null인경우, 로그인 페이지로 리다이렉션한다. 이때의 도메인 주소는 blabla.com 이다. 즉, 로그인시 해당 도메인(blabla.com)에서 쿠키를 요청해서 서버로부터 쿠키를 저장하라고 Set-Cookie헤더를 응답받게 되는데, localhost 도메인으로 돌아가서 아래 코드처럼 쿠키를 get해오려고 해당 클라이언트에 요청을 해버리니까 서버에서 undefined를 반환하는 것이다.

// server-component
import {cookies} from 'next/headers';

import BrandMenu from '../../components/brands/brandMenu';

// 페이지는 경로 세그먼트에 고유한 UI다.
export default function BrandsPage() {
    const cookieStore = cookies();
    const accessToken = cookieStore.get('accessToken');
    // toDo 로그인을 해도 undefined가 찍힌다.
    console.log('accessToken', accessToken);

    return <BrandMenu />;
}

더 나아가서 🗯️


근데 여기서 undefined를 반환하는 서버는 무엇일까? 아래와 같이 공식문서에서 cookies()함수는 동적함수이고,

아래와 같이 공식문서에서 동적함수는 캐시된다는데 그럼 캐시서버일까?

앞서 정리했듯이, 서버와 통신해야 cookie를 받을 수 있다. nextJS에선 서버 컴포넌트와 클라이언트 컴포넌트가 나누어져있다.

우선 ssr이란 서버 사이드에서 웹 문서를 만들어서 클라이언트로 서빙하는 것을 말한다. 즉, 서버 사이드에서 만들어진 문서는 javascript가 없이도 페이지를 렌더링할 수 있다. 하지만 정적으로만 화면을 구성할 수 있다. 그래서 화면의 레이아웃을 변경하려면 서버 사이드에서 html을 계속 만들어야하기 때문에 서버 리소스가 많이들게 된다.

그렇기 때문에 reactJS가 클라이언트 사이드(브라우저)에서 javascript로 이미 렌더링된 html페이지를 조작하는 것이다.

결국 nextJS를 사용하게 되면 nextJS가 주체로 html파일을 클라이언트 사이드에 서빙하고, 그 다음에 reactJS가 주체로 javascript파일을 클라이언트 사이드에 서빙한다. 이후 js코드들이 이전에 보내진 html dom요소에 매칭되게 되는데 이게 hydration이다.

다시 돌아와서 서버에서 제공하는 쿠키를 가져오려면 헤더를 통해 가져와야하는데, 이때는 서버 사이드, 클라이언트 사이드 모두 가능하지만 적어도 해당 서버를 거쳐서 받아와야만 한다. 즉 서버를 거치지 않고 다이렉트로 브라우저로 접근하면 서버에서는 undefined를 반환할 수 밖에 없는 것이다.

여기서 서버는 nextJS이고, nextJS는 서버와 통신을 한 적이 없어서 쿠키의 존재를 모른다. 그런데 blabla.com서버를 거쳐서 오게되면 해당서버에서 쿠키를 .blabla 도메인으로 공유하기 때문에 같은 도메인인 test.blabla에서는 쿠키를 알 수 있게된다. 하지만 localhost도메인에서는 이를 절대 알 수 없다. 그래서 undefined를 반환했던 것이다.

☑️ 참조


https://blog.logrocket.com/guide-cookies-next-js/
https://developer.mozilla.org/ko/docs/Web/HTTP/Cookies

profile
Superduper-India

4개의 댓글

comment-user-thumbnail
2023년 7월 31일

많은 도움이 되었습니다, 감사합니다.

1개의 답글
comment-user-thumbnail
2023년 12월 28일

쿠키를... 굽는 데서 부터 막혀있네요. ㅎㅎ

next의 cookies 모듈은 route handler나 server action에서만 set이 가능한데 로그인 하실 때 server action이나 route handler로 넘겨서 하신 건가요?

1개의 답글