RefreshToken

hyeseon han·2021년 10월 19일
0

AccessToken은 사용자의 로그인 정보를 담아주는 데이터이다. 이 데이터는 만료기간이 정해져 있어 일정 시간동안만 사용할 수 있다.

만료기간이 지나고 사용자가 로그인 정보가 필요한 페이지에 접근하려고 할 때, 새로운 AccessToken을 받아오는 과정을 RefreshToken이라 한다.

예) AccessToken 만료 기간이 1시간 가정
1시간이 지나면 발급받은 AccessToken은 시간 만료로 인해 사용할 수 없는 토큰이 된다.
토큰을 새로 발급받기 위해서 다시 로그인을 해야한다. 이와 같이 1시간마다 로그인을 다시 해야하는 불편함을 해결하기 위해서,

AccessToken을 발급할 때 RefreshToken을 함께 발급한다.
AccessToken이 만료되는 시점에 RefreshToken을 통해서 다시 로그인할 필요없이 RefreshToken을 통해서 새로운 AccessToken을 받을 수 있다.

📌 RefreshToken도 만료 기간이 존재한다. 따라서 새로운 RefreshToken을 발급받는 과정이 필요하다.

// getAccessToken.ts
// useQuery, useMutation 이 app.tsx에서 안된다.

import { gql } from "@apollo/client";
import { GraphQLClient } from "graphql-request";
import { Dispatch, SetStateAction } from "react";

const RESTORE_ACCESS_TOKEN = gql`
  mutation restorAccessToken {
    restoreAccessToken {
      accessToken
    }
  }
`;

// 1.refreshToken으로 새로운 accessToken 재발급 받기
export async function getAcessToken(
  setAccessToken: Dispatch<SetStateAction<string>>
) {
  try {
    const graphQLClient = new GraphQLClient(
      "https://backend03.codebootcamp.co.kr/graphql",
      { credentials: "include" }
    );
    const result = await graphQLClient.request(RESTORE_ACCESS_TOKEN);
    const newAccessToken = result.restoreAccessToken.accessToken;
    setAccessToken(newAccessToken);
    return newAccessToken;
  } catch (error) {
    console.log(error.message);
  }
}
// app.js

import "antd/dist/antd.css";
import {
  ApolloClient,
  ApolloProvider,
  InMemoryCache,
  ApolloLink,
} from "@apollo/client";
import { onError } from "@apollo/client/link/error";
import { createUploadLink } from "apollo-upload-client";
import { createContext, useEffect, useState } from "react";
import { getAcessToken } from "../src/commons/libraries/getAccessToken";

export const GlobalContext = createContext(null);
function MyApp({ Component, pageProps }) {
  const [accessToken, setAccessToken] = useState("");
  const [userInfo, setUserInfo] = useState({}); // {} 처음에는 빈객체

  const value = {
    accessToken: accessToken,
    setAccessToken: setAccessToken,
    userInfo: userInfo,
    setUserInfo: setUserInfo,
  };

  useEffect(() => {
    if (localStorage.getItem("refreshToken")) getAcessToken(setAccessToken);
  }, []);

  const errorLink = onError(({ grahpQLErrors, operation, forward }) => {
    if (grahpQLErrors) {
      for (const err of grahpQLErrors) {
        if (err.extensions?.code === "UNAUTHENTICATED") {
          operation.setContext({
            headers: {
              ...operation.getContext().headers,
              authorization: `Bearer ${getAcessToken(setAccessToken)}`,
            },
          });

          return forward(operation);
        }
      }
    }
  });

  const uploadLink = createUploadLink({
    uri: "https:--------------------",
    headers: { authorization: `Bearer ${accessToken}` }, //템플릿리터럴
    // headers: { authorization: `Bearer ${localStorage.getItem("accessToken")}` },
    credentials: "include",
  });

  const client = new ApolloClient({
    link: ApolloLink.from([errorLink, uploadLink]),
    cache: new InMemoryCache(),
  });

  return (
		...
  );
}

export default MyApp;
// login.js

import { useState, useContext } from "react";
import { gql, useMutation } from "@apollo/client";
import { GlobalContext } from "../_app";
import { useRouter } from "next/router";

const LOGIN_USER = gql`
	...
`;

export default function LoginPage() {
  const router = useRouter();

  const { setAccessToken, accessToken } = useContext(GlobalContext);

  const [myEmail, setMyEmail] = useState("");
  const [myPassword, setMyPassword] = useState("");

  const [loginUserExample] = useMutation(LOGIN_USER);

  function onChangeEmail(event) {
    setMyEmail(event.target.value);
  }

  function onChangePassword(event) {
    setMyPassword(event.target.value);
  }

  // onClick 했을 때  loginUser 실행 -> email, password 사용 -> async, await 로 return을 기다린다.
  async function onClickLogin() {
    const result = await loginUserExample({
      variables: {
        email: myEmail,
        password: myPassword,
      },
    });
    localStorage.setItem("refreshToken", "true"); setAccessToken(result.data?.loginUserExample.accessToken);
    router.push("/32-02-login-success");
  }

  return (
    <>
      이메일: <input type="text" onChange={onChangeEmail} />
      <br />
      비밀번호: <input type="password" onChange={onChangePassword} />
      <br />
      <button onClick={onClickLogin}>로그인하기</button>
    </>
  );
}

0개의 댓글