[TIL] 10월 19일 RefreshToken

기록하며 공부하자·2021년 10월 23일
0

로그인시 사용자의 정보를 AccessToken에 저장해 로그인기능을 구현했었고, 이 AccessToken은 일정시간(대략 1시간 ~ 2시간)이 지나면 만료되어 다시 로그인을 하도록 구현했었다.

AccessToken의 만료시간 1분전에 로그인했다면 무조건 1분뒤에는 로그아웃이 되어야 하고 하루에도 몇번씩 다시 로그인을 해야한다.

이러한 불편함을 줄이기 위해 AccessToken의 만료시간을 늘려버리면 localStorage에 저장되어 있기때문에 해킹위험에 노출된다.

RefreshToken

기존에 구현했었던 localStorage에 저장하는 방법은 매우 위험하며 비효율 적이다.

그래서 localStorage가 아닌 쿠키에 저장하여 RefreshToken으로 로그인을 권한을 검사하는 방법으로 변경하면 민감한 AccessToken의 정보를 저장할 필요도 없으며, RefreshToken의 만료시간(평군 2개월)은 굉장히 길기 때문에 아주 효율적이다.

app.js 부분 발췌 코드

const [accessToken, setAccessToken] = useState("");
  const [userInfo, setUserInfo] = useState({});
  const value = {
    accessToken: accessToken,
    setAccessToken: setAccessToken,
    userInfo: userInfo,
    setUserInfo: setUserInfo,
  };
  useEffect(() => {
    // localStorage.clear();
    // const accessToken = localStorage.getItem("accessToken") || "";
    // setAccessToken(accessToken);
    if (localStorage.getItem("refreshToken")) getAccessToken(setAccessToken);
  }, []);
  const errorLink = onError(({ graphQLErrors, operation, forward }) => {
    if (graphQLErrors) {
      for (const err of graphQLErrors) {
        if (err.extensions?.code === "UNAUTHENTICATED") {
          // operation.getContext().headers
          // 기존에 날렸던 쿼리의 헤더정보들
          operation.setContext({
            headers: {
              ...operation.getContext().headers,
              authorization: `Bearer ${getAccessToken(setAccessToken)}`,
              // 기존 쿼리중 엑세스토큰만 바꿔서 다시날림
            },
          });
          return forward(operation);
        }
      }
    }
  });
  // 그라프큐엘 에러...., 어떤 쿼리를 썼는지, 다시날릴때 사용할 포워드
  // 반복문은 공식문서에 따라 작성
  const uploadLink = createUploadLink({
    uri: "https://backend03.codebootcamp.co.kr/graphql",
    headers: { authorization: `Bearer ${accessToken}` },
    credentials: "include",
  });
  const client = new ApolloClient({
    link: ApolloLink.from([errorLink, uploadLink]),
    cache: new InMemoryCache(),
  });

localStorage와 마찬가지로 app.js 부분에서 설정을 해줘야 한다.
조금 변경된 부분이 있다면 RefreshToken이 만료되면 onError라는 에러로 에러를 캐치해 다시 RefreshToken을 넣어줘야 하는 과정이 추가되어 uploadLink와 errorLink가 포함된다.

GetAccesToken.ts 전체 코드

import { gql } from "@apollo/client";
import { GraphQLClient } from "graphql-request";
import { Dispatch, SetStateAction } from "react";
const RESTORE_ACCESS_TOKEN = gql`
  mutation restoreAccessToken {
    restoreAccessToken {
      accessToken
    }
  }
`;
// refreshtoken으로 새로운 accessToken 재발급 받기
export async function getAccessToken(
  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 에 export한 getAccestoken 코드이다.
쿼리를 불러오고 setAccessToken에 새로운 accessToken을 넣는 부분이다.

로그인 페이지 발췌 코드

import { useContext, useState } from "react";
import { gql, useMutation } from "@apollo/client";
import { GlobalContext } from "../_app";
import { useRouter } from "next/router";
const LOGIN_USER = gql`
  # mutation loginUser($email : String!, $password: String! ){
  #     loginUser(email: $email, password:$password){
  #         accessToken
  #     }
  # }
  mutation loginUserExample($email: String!, $password: String!) {
    loginUserExample(email: $email, password: $password) {
      accessToken
    }
  }
`;
export default function LoginPage() {
  const router = useRouter();
  const { setAccessToken } = 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);
  }
  async function onclickLogin() {
    const result = await loginUserExample({
      variables: {
        email: myEmail,
        password: myPassword,
      },
    });
    // console.log(result.data?.loginUser.accessToken);
    // localStorage.setItem("accessToken", result.data.loginUser.accessToken);
    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>
    </>
  );
}

app.js 설정이 끝난후 로그인페이지에서 RefreshToken여부를 검사해 true 여부를 검사해주면 RefrehToken이 true인 경우 정상진행하게 된다.

RefreshToken이 만료되었다면 에러를 감지해 다시 RefrehToken을 발급받는다.

profile
프론트엔드 개발자 입니다.

0개의 댓글