30) 이제 한시간에 한번씩 로그인 다시 안 해도 된다

huiju·2022년 6월 20일
0
post-thumbnail

React

이번주 수업내용

오늘 수업내용

RefreshToken

AccessToken 데이터는 일정 시간 동안만 사용할 수 있도록 만료 기한이 정해져 있는데,

만료 기한이 지나고 사용자가 로그인 정보가 필요한 페이지에 접근하려고 하면
백엔드에서 미리 지정해둔 경로로 redirect 되거나 에러가 뜨게 된다

그렇기 때문에 AccessToken의 만료 기한이 지나면 새로운 AccessToken을 받아와야 하고 이러한 과정에 사용되는 토큰을 RefreshToken 이라고 한다

사용자가 로그인을 하면 백엔드에서 AccessToken, RefreshToken을 받아오게 되는데, AccessToken은 1~2시간 정도의 짧은 만료 기간을 가지고 있고 RefreshToken은 2주~1개월 정도의 긴 만료 기한을 가지고 있다

=> RefreshToken을 쿠키에 담아서 받아오기

  1. AccessToken을 발급할 때 RefreshToken을 함께 발급받는다.
  2. 인가 과정에서 AccessToken이 만료돼서 오류가 난다면 onError 함수를 통해서 알 수 있음
  3. RefreshToken을 통해 로그인 과정 없이 새로운 AccessToken을 받아올 수 있다.
  4. 발급받은 AccessToken을 state에 저장하고
  5. 2번에서 실패했던 쿼리(API)를 재시도함

만약 RefreshToken까지 만료됐다면?
다시 로그인을 진행해서 새로운 RefreshToken을 가져와야 한다

RefreshToken 실습

브라우저의 Cookies 탭에서 RefreshToken을 찾아야 하는데(RefreshToken에 Secure 옵션이 적용되어 있기 때문에) Apollo Setting의 GraphQl 설정을 변경해줘야 한다

1) uploadLink의 uri 경로를 http에서 https 로 바꾸기
2) 민감한 정보를 승인한다는 뜻의 credentials: "include" 옵션 추가

const uploadLink = createUploadLink({
  uri: "https://backend07.codebootcamp.co.kr/graphql",
  headers: { Authorization: `Bearer ${accessToken}` },
  credentials: "include",
});
  1. RefreshToken 확인

    로그인 후 네트워크 탭에서 loginUser API 를 열고, Cookies 탭에서 refreshToken을 찾기

  1. Apllo Setting에 errorLink 생성 및 구조 기반 설정
    Apollo-client에서 제공하는 onError 기능 사용
// restoreAccessToken graphQl 발급받기
  const RESTORE_ACCESS_TOKEN = gql`
    mutation restoreAccessToken {
      accessToken
    }
  `;

  // AccessToken 만료시 오류 찾을 수 있도록 onError 함수 사용 (위에 임포트하기)
  const errorLink = onError(async ({ graphQLErrors, operation, forward }) => {
    // 에러났을 때 실행시킬 콜백함수 집어넣기          operation(방금 실패한 쿼리 정보), forward(요청하는거)

    // 1.에러 캐치
    if (graphQLErrors) {
      for (const err of graphQLErrors) {
        // 2.해당 에러가 토큰 만료 에러인지 체크(UNAUTHENTICATED)
        if (err.extensions.code === "UNAUTHENTICATED") {
          // 3.refreshToken으로 accessToken 재발급 받기

          // refreshToken을 사용하기 위해서는 graphQL 요청을 보내야 하는데,
          // errorLink를 생성하는 코드는 ApolloProvider 바깥(=현재 위치 ApolloProvider)에 있기 때문에
          // useQuery나 useApolloClient등을 이용해 graphQL 요청을 보낼 수가 없음
          // 이러한 문제를 해결하기 위해서 graphql-request 라이브러리(apollo setting없이 graphql을 axios처럼 사용할 수있는)를 사용

          // 3-1) GraphQlClient import
          const graphQlClient = new GraphQLClient(
            "https://backend07.codebootcamp.co.kr/graphql"
          );
          // 3-2) RESTORE_ACCESS_TOKEN gql을 요청한 뒤 반환되는 결과값을 result에 담기
          const result = await graphQlClient.request(RESTORE_ACCESS_TOKEN);
          // 3-3) 재발급 받은 accessToken 을 newAccessToken에 저장하기
          const newAccessToken = result.restoreAccessToken.accessToken;
          // 3-4) newAccessToken 저장하기
          setAccessToken(newAccessToken); // RecoilState도 새로운 토큰으로 바꿔주기

          // 4.재발급 받은 accessToken으로 방금 실패한 쿼리 재요청하기
          // 4-1) operation(방금 실패했던 쿼리)정보는 모두 유지한 채 토큰만 새걸로 바꿔치기해줌
          operation.setContext({
            // 헤더 부분만 건드릴거야
            headers: {
              // Authorization 만 작성해주면 다른건 다 지워지고 새 토큰으로 바꿔치기 한 것만 남기 때문에
              // 기존 헤더는 그대로 유지한 채 가지고 와서 Authorization 만 새 토큰으로 바꿔주기
              ...operation.getContext().headers, // operation 정보들을 가져오기
              Authorization: `Bearer ${newAccessToken}`, // accessToken만 바꿔치기
            },
          });
          // 4-2) 변경된 operation 재요청하기
          return forward(operation);
        }
      }
    }
  });

코드가 너무 길다...

  1. 코드 길이 줄여서 정리해주기
export async function getAccessToken() {
  try {
    // 1. GraphQlClient import
    const graphQlClient = new GraphQLClient(
      "https://backend07.codebootcamp.co.kr/graphql"
    );
    // 2. RESTORE_ACCESS_TOKEN gql을 요청한 뒤 반환되는 결과값을 result에 담기
    const result = await graphQlClient.request(RESTORE_ACCESS_TOKEN);
    // 3. 재발급 받은 accessToken 을 newAccessToken에 저장하기
    const newAccessToken = result.restoreAccessToken.accessToken;

    return newAccessToken;
  } catch (error) {
    console.log(error.message);
    // refreshToken까지 만료된 상황이기 때문에 로그인 페이지로 이동시켜 줘야 함
  }
}
getAccessToken().then((newAccessToken) => {
            // 3-2) newAccessToken 저장하기
            setAccessToken(newAccessToken); // RecoilState도 새로운 토큰으로 바꿔주기

            // 4.재발급 받은 accessToken으로 방금 실패한 쿼리 재요청하기
            // 4-1) operation(방금 실패했던 쿼리)정보는 모두 유지한 채 토큰만 새걸로 바꿔치기해줌
            operation.setContext({
              // 헤더 부분만 건드릴거야
              headers: {
                // Authorization 만 작성해주면 다른건 다 지워지고 새 토큰으로 바꿔치기 한 것만 남기 때문에
                // 기존 헤더는 그대로 유지한 채 가지고 와서 Authorization 만 새 토큰으로 바꿔주기
                ...operation.getContext().headers, // operation 정보들을 가져오기
                Authorization: `Bearer ${newAccessToken}`, // accessToken만 바꿔치기
              },
            });
            // 4-2) 변경된 operation 재요청하기
            return forward(operation);
          });

GraphQL의 실체

graphql-request 라이브러리를 사용해 restoreToken API를 요청하는 코드를 보면 Axios를 이용한 Rest API 요청 형태와 유사한데, 이것은 GraphQL도 사실은 Rest-API의 일종이기 때문

// 게시글 조회
axios.get("API 주소")

// 게시글 등록
axios.post("API 주소", { 데이터 })
// 게시글 조회
axios.get("https://koreanjson.com/posts/1")

// 게시글 등록
axios.post(
	"https://koreanjson.com/boards",
	{ writer: "철수", title: "제목!!", contents: "내용!!" }
)

일반적인 Rest API는 필요한 기능 별로 서로 다른 endpoint를 가지고 있는데, 이렇게 되면 서비스의 규모가 커질수록 endpoint의 수가 많아지기 때문에 graphql이라는 하나의 endpoint에 모든 api를 통합하는 방식으로 사용하는 것(Graphql은 query와 mutation 모두 post 방식으로 데이터가 요청됨)

axios.post("[API 주소]/graphql", { 
	aaa: "GraphQL 요청"
})

언더페칭

언더페칭은 필요한 데이터보다 적은 양을 가져온다는 의미

일반적인 Rest API의 경우 createBoard 요청을 보낸 뒤 fetchBoard 요청을 따로 보내주어야 하는데, grahpql이라는 하나의 endpoint를 이용할 경우, 한 번의 요청만 보내면 된다

axios.post("[API 주소]/graphql", { 
	aaa: "
		createBoard(){
			// 요청하는 내용
		}
		fetchBoard(){
			// 요청하는 내용
		}
	"
})

오버페칭

오버페칭은 필요하지 않은 데이터까지 가져온다는 의미

graphQl 방식으로 데이터를 요청할 경우, 응답 데이터 중 필요한 데이터만 골라서 받아오는 것이 가능

이렇게 graphQl은 rest api의 단점 두가지(언더페칭, 오버페칭)를 보완함

++
graphql 참고

profile
어제보다 오늘 발전하는 프론트엔드 개발자

0개의 댓글