로그인을 하지 않은 사용자가 이용할 수 없는 페이지에 접근하면
"로그인 후 이용해주세요." 라는 Alert과 로그인 화면으로 이동시키는 HOC를 만들어보자!
사실 전에 만들었는데 잘 안돼서 ㅋ_ㅋ A/S 고고
보안 목적 상 accessToken은 state에 담아놓고, accessToken을 재발급 받을 수 있는 refreshToken이 쿠키에 저장되어 있는 상황이다.
accessToken은 state에 저장되어 있기 때문에 새로고침을 할 때마다 state는 초기화되어 accessToken은 없는 상태가 된다!!
그래서 useEffect 안에서 accessToken을 재발급 받는 함수를 통해 accessToken을 재발급 받아오도록 하고, 재발급 받는 함수를 요청했는데도 accessToken이 없으면(refreshToken이 만료되었거나, 로그아웃을 해서 refreshToken이 없는 경우) 권한이 없다고 판단하고 로그인 화면으로 보내도록 할 것이다.
src > commons > libraries
getAccessToken
import { GraphQLClient, gql } from "graphql-request";
// accessToken을 재발급해주는 쿼리
const RESTORE_ACCESS_TOKEN = gql`
mutation restoreAccessToken {
restoreAccessToken {
accessToken
}
}
`;
export async function getAccessToken() {
/* 2-1. refreshToken으로 accessToken을 재발급 받는다. */
try {
const graphQLClient = new GraphQLClient(
"https://backend06.codebootcamp.co.kr/graphql",
{ credentials: "include" }
); // endpoint를 넣어준다. https로 넣어줘야 한다!!!
const result = await graphQLClient.request(RESTORE_ACCESS_TOKEN); // 쿼리를 넣어준다.
const newAccessToken = result.restoreAccessToken.accessToken;
return newAccessToken;
} catch (error) {
if (error instanceof Error) console.log(error.message);
}
}
src > commons > store
store
import { atom } from "recoil";
import { getAccessToken } from "../libraries/getAccessToken";
export const accessTokenState = atom({
key: "accessTokenSate",
default: "",
});
src > components > commons > apollo
ApolloSetting
const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
useEffect(() => {
/* accessToken 재발급 받아서 state에 넣어주기 */
getAccessToken().then((newAccessToken) => {
setAccessToken(newAccessToken);
});
}, []);
src > components > commons > hocs
withAuth
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useRecoilState } from "recoil";
import { getAccessToken } from "../../../commons/libraries/getAccessToken";
import { accessTokenState } from "../../../commons/store";
// @ts-ignore
export const withAuth = (Component) => (props) => {
const [accessToken] = useRecoilState(accessTokenState);
const router = useRouter();
useEffect(() => {
if (!accessToken) {
getAccessToken().then((newAccessToken) => {
if (!newAccessToken) {
alert("로그인 후 이용해주세요.");
router.push("/login");
}
});
}
}, []);
return <Component {...props} />;
};
// Component가 화면에 실행된다.
// 결과는 똑같이 Component가 화면에 보이지만, 다른 점은 중간에 logic이 추가된 것이다.
위 방식은 요청이 getAccessToken에서도 이루어지고, 이 HOC에서도 이루어져서
총 두번 이루어지므로 비효율적이다.
(HOC에서는 요청을 안 보내게 하면
getAccessToken을 해서 받아오는 동안에 accessToken이 없어서
withAuth에서 권한이 없다고 판단되어 로그인 화면으로 이동해버린다...)
useRecoilValueLoadable
을 활용해서 state가 요청중인지 요청이 끝났는지 상황을 알 수 있다.recoilState를 만들 때, atom은 특정 데이터를 저장하는 반면 selector는 기능이 들어갈 수 있다.
글로벌 함수처럼 쓸 수 있게 된다!!
src > commons > store
import { selector } from "recoil";
import { getAccessToken } from "../libraries/getAccessToken";
// atom은 특정 데이터, selector는 기능이 들어갈 수 있다.
// 이 함수를 여러 컴포넌트에서 공유할 수 있게 된다. like 글로벌 함수!
export const restoreAccessTokenLoadable = selector({
key: "restoreAccessTokenLoadable",
get: async () => {
const newAccessToken = await getAccessToken();
return newAccessToken;
},
});
src > components > commons > apollo
ApolloSetting
const restoreAccessToken = useRecoilValueLoadable(restoreAccessTokenLoadable);
useEffect(() => {
restoreAccessToken.toPromise().then((newAccessToken) => {
setAccessToken(newAccessToken);
});
}, []);
src > components > commons > hocs
withAuth
useEffect(() => {
if (!accessToken) {
restoreAccessToken.toPromise().then((newAccessToken) => {
if (!newAccessToken) {
alert("로그인 후 이용 가능합니다.");
router.push("/login");
}
});
}
}, []);