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>
</>
);
}