인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

작성자 없음

작성자 정보가 삭제된 글입니다.

[코드캠프] 부트캠프에서 만든 고농축 프론트엔드 코스

30-01-login-refreshtoken / login-refreshtoken-success

refreshToken 저장의 문제

해결된 질문

작성

·

72

·

수정됨

0

아래에 질문이 있으나 해결안이 없어 다시 문의드립니다.

refreshToken, 쿠키는 정상적으로 전달되고 있습니다.

setCookie도 나옵니다.

image.png

 

image.png

그러나, Application에 나타나지 않습니다.

image.png



그리고 조금 기다렸다가 acessToken을 재발급하려고 클릭하면 아래와 같은 메시지가 나타납니다.

image.png

 

크롬에서 써드파티 쿠키를 다 accept하도록 설정이 되어있고, 시크릿 모드에서도 진행해봤고 사파리에서도 동일합니다.

 

클릭하여 나타난 통신기록을 총 3번입니다.

  1. 첫번째 통신은 accessToken만료로 발생한 것으로 보입니다.

네트워크 통신기록을 살펴보면 UNAUTHENTICATED가 발생된 것을 알수 있습니다.

image.png

 

  1. 두번째 통신은 restoreAccessToken의 뮤테이션입니다. 헤더와 페이로드는 아래와 같습니다. jwt가 첨부되지 않았다는 부분입니다.


    image.pngimage.pngimage.pngimage.png

     

  2. 3번째 통신의 Header와 payload, response입니다. 이 부분은 주어진 조건으로 사용자 정보를 불러오는 query같습니다. 사용자에 관련된 정보가 없이 시도되어 _id null이 표시된것 같습니다.


    image.pngimage.pngimage.png


    인터넷에 검색해보았지만 잘 모르겠습니다. axios통신으로 refreshToken을 직접 저장해주는 코드를 보긴 했는데, graphql은 설정만 바꾼다고 하셔서 refreshToken을 어떻게 처리하는지 잘 모르겠습니다.

    관련해서 검색을 통해 https로 환경을 바꾸면 될거다 하여, localhost:3000을 mkcert로 https로 환경을 바꾸려고 해봤는데 설치만 하고 아래의 글을 발견해서 잠깐 멈추었습니다. https://shingy.tistory.com/46

     

     

    _id null의 문제가 local에 받아온 쿠키를 저장하지 못하는 문제인것 같은데, 어떻게 처리할수있는 방법이 있나 싶어서 여쭤봅니다.

     

  3. 코드

     

    getAccessToken의 코드

     

    import { GraphQLClient, gql } from "graphql-request";
    import { IMutation } from "../../../commons/types/generated/types";
    
    const RESTORE_ACCESS_TOKEN = gql`
      mutation {
        restoreAccessToken {
          accessToken
        }
      }
    `;
    
    export const getAccessToken = async (): Promise<string | undefined> => {
      try {
        const graphQLClient = new GraphQLClient(
          "https://backend-practice.codebootcamp.co.kr/graphql",
          { credentials: "include" },
        );
        const result =
          await graphQLClient.request<Pick<IMutation, "restoreAccessToken">>(
            RESTORE_ACCESS_TOKEN,
          );
        const newAccessToken = result.restoreAccessToken.accessToken;
        return newAccessToken;
      } catch (error) {
        if (error instanceof Error) console.log(error);
      }
    };
    

    아폴로 클라이언트 코드

 import {
  ApolloClient,
  InMemoryCache,
  ApolloProvider,
  ApolloLink,
  fromPromise,
} from "@apollo/client";
import { createUploadLink } from "apollo-upload-client"; 
import { useRecoilState } from "recoil";
import { accessTokenState } from "../../../commons/store";
import { useEffect } from "react";
import { ErrorResponse, onError } from "@apollo/client/link/error";
import { getAccessToken } from "../libraries/getAccessToken";

const GLOBAL_STATE = new InMemoryCache();
interface IApolloSettingProps {
  children: JSX.Element;
}

export default function ApolloSetting(props: IApolloSettingProps) {
  const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
 
  useEffect(() => {
    const result = localStorage.getItem("accessToken");
    console.log(result);
    console.log("현재는 브라우저입니다");
    setAccessToken(result ?? "");
  }, []);

  const errorLink = onError(({ graphQLErrors, operation, forward }) => {
    //1-1.에러캐치
    if (typeof graphQLErrors !== "undefined") {
      for (const err of graphQLErrors) {
        //1-2.토큰 만료에러인지 체크
        if (err.extensions?.code === "UNAUTHENTICATED") {
          return fromPromise(

            getAccessToken()
              .then((newAccessToken) => {
                setAccessToken(newAccessToken ?? "");
                operation.setContext({
                  headers: {
                    ...operation.getContext().headers,
                    Authorization: `Bearer ${newAccessToken}`,
                  },
                });
              })
              .catch((err) => {
                console.log("오류입니다");
              }),
          ).flatMap(() => forward(operation));
        }
      }
    }
  });

  const uploadLink = createUploadLink({
    uri: "https://backend-practice.codebootcamp.co.kr/graphql",
    headers: {
      Authorization: `Bearer ${accessToken}`,
      credentials: "include",
    },
  });

  const client = new ApolloClient({
    link: ApolloLink.from([errorLink, uploadLink]),
    uri: "http://backend-practice.codebootcamp.co.kr/graphql",
    cache: GLOBAL_STATE, 
  });
  return <ApolloProvider client={client}>{props.children}</ApolloProvider>;
}

로그인 success의 코드

import { gql, useApolloClient, useQuery } from "@apollo/client";
import type { IQuery } from "../../../src/commons/types/generated/types";
import { wrapAsync } from "../../../src/components/commons/libraries/asyncFunt";

const FETCH_USER_LOGGED_IN = gql`
  query fetchUserLoggedIn {
    fetchUserLoggedIn {
      email
      name
    }
  }
`;

export default function LoginPage() {
  const client = useApolloClient();

  const onClickButton = async (): Promise<void> => {
    const result = await client.query({
      query: FETCH_USER_LOGGED_IN,
    });
    console.log(result);
  };

  return (
    <>
      {/* <div>{data?.fetchUserLoggedIn.name}님, 환영합니다.</div> */}
      <div>환영</div>
      <button onClick={wrapAsync(onClickButton)}>클릭하세요</button>
    </>
  );
}

답변 2

1

인프런 AI 인턴님의 프로필 이미지

안녕하세요, 인프런 AI 인턴입니다. 이지은님.

문의주신 내용을 보니 refreshToken이 정상적으로 전달되지만 브라우저의 “Application” 탭에서 쿠키가 보이지 않는 문제를 겪고 계시군요. 이런 문제는 설정에서 비롯된 경우가 많습니다. 문제 해결을 돕기 위해 몇 가지 가능성을 고려해보겠습니다.

문제 분석 및 해결 제안:

  1. Secure Cookie 설정: refreshToken이 Secure 속성을 가지고 있을 경우, HTTPS를 통해서만 전송될 수 있습니다. 개발 환경(예: localhost)에서 이 속성을 사용하는 경우, 쿠키가 저장되지 않을 수 있습니다. 이 경우, HTTPS 환경에서 요청하게 하거나, 개발 환경에서 Secure 속성을 제거해 시도해 보세요. 이미 참고하신 mkcert로 HTTPS 설정이 도움이 될 수 있습니다.

  2. CORS 설정 및 credentials 옵션: GraphQL 클라이언트를 설정할 때 credentials: "include" 옵션이 제대로 적용되었는지 다시 한 번 확인해보세요. 이 옵션은 쿠키가 클라이언트와 서버 간에 교환될 수 있게 합니다. GraphQLClient와 ApolloClient에서 모두 이 옵션이 설정되어 있는지 체크해보세요.

  3. 쿠키 도메인 확인: 쿠키의 도메인이 지금 요청하고 있는 도메인과 일치해야 합니다. localhost 대신 127.0.0.1을 사용하거나 반대로 시도해 보세요. 도메인 불일치로 인해 쿠키가 브라우저에 나타나지 않는 경우가 있을 수 있습니다.

  4. 기타 요인:

    • 브라우저 설정: 모든 쿠키가 허용되도록, 특히 크롬의 ‘사이트 설정’에서 쿠키와 기타 사이트 데이터를 확인해 보세요.
    • 조정된 Network 패널: 브라우저의 Network 탭에서 “Disable cache” 옵션을 해제하여 캐시 문제를 제거해보세요.
  5. 토큰 전송 상태 확인: 첫 번째 통신에서 UNAUTHENTICATED가 발생하고 있는 것으로 보입니다. 이 이유로는 accessToken이 누락되었거나 유효하지 않을 경우가 있습니다. setContext에서 Authorization 헤더를 확인하고, 제대로 값이 전달되고 있는지 재확인하시기 바랍니다.

이러한 관점에서 문제를 접근할 수 있도록 돕기 위해 몇 가지 조정을 시도해 보시길 권장드립니다. 쿠키 설정 및 클라이언트 설정 관련 튜토리얼이나 문서를 추가로 참조하시면 도움이 될 것입니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

0

이지은님의 프로필 이미지

질문중에 제가 실수한 부분을 발견했습니다 ^^ uploadLink에서 credentials설정을 요소로서 넣어주어야 하는데.. headers안의 요소로서 넣은것을 확인했습니다.

혹시나 다른 분들도 이런 문제를 겪으실지 몰라서 해당 질문은 남겨두겠습니다.

노원두님의 프로필 이미지
노원두
지식공유자

안녕하세요! 지은님!

소중한 정보 공유 감사드려요!^^
다른 분들도 많은 도움 받아 가실 것 같아요^^

작성자 없음

작성자 정보가 삭제된 글입니다.

질문하기