로그인 인증 이후에는 로그인 상황에 따른 권한 분기를 하게 되는데 예를 들어 로그인을 한 사람과 로그인을 하지 않은 사람, 운영자로 로그인한 사람, 판매자로 로그인한 사람, 거래처사장님으로 로그인한 사람 등 다양하게 권한을 분리한다.
useEffect(() => {
if (localStorage.getItem("accessToken") === null) {
alert("로그인 후 이용 가능합니다!!");
void router.push("/23-03-login-check");
}
}, []);
useEffect로 권한이 없다면 로그인창으로 보내버리기
// withAuth.tsx
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useRecoilValueLoadable } from "recoil";
import { restoreAccessTokenLoadable } from "../../../commons/stores";
export const withAuth = (ABC: any) => (props: any) => {
const router = useRouter();
const aaa = useRecoilValueLoadable(restoreAccessTokenLoadable);
useEffect(() => {
void aaa.toPromise().then((newAccessToken) => {
if (newAccessToken === undefined) {
alert("로그인 후 이용 가능합니다!!!");
void router.push("/30-02-login-refreshtoken-success");
}
});
}, []);
return <ABC {...props} />;
};
// stores/index.ts
export const restoreAccessTokenLoadable = selector({
key: "restoreAccessTokenLoadable",
get: async () => {
const newAccessToken = await getAccessToken();
return newAccessToken;
},
});
withAuth 파일을 권한분기시 임포트하면 된다.
그런데 useAuth가 더 편리하다.
useAuth라는 커스텀 훅을 만들어서 넣어보자.
// apollo/index.tsx
import {
ApolloClient,
ApolloLink,
ApolloProvider,
fromPromise,
InMemoryCache,
} from "@apollo/client";
import { createUploadLink } from "apollo-upload-client";
import { useEffect } from "react";
import { useRecoilState, useRecoilValueLoadable } from "recoil";
import {
accessTokenState,
restoreAccessTokenLoadable,
} from "../../../commons/stores";
import { onError } from "@apollo/client/link/error";
import { getAccessToken } from "../../../commons/libraries/getAccessToken";
interface IApolloSettingProps {
children: JSX.Element;
}
const GLOBAL_STATE = new InMemoryCache();
export default function ApolloSetting(props: IApolloSettingProps) {
const [accessToken, setAccessToken] = useRecoilState(accessTokenState);
const aaa = useRecoilValueLoadable(restoreAccessTokenLoadable);
void aaa.toPromise().then((newAccessToken) => {
setAccessToken(newAccessToken);
});
}, []);
// 로그인 시 리프레시 토큰
// 왜 graphQLErrors operation forward 가 들어오는가
const errorLink = onError(({ graphQLErrors, operation, forward }) => {
// 1. 에러를 캐치
// 에러가 동시에 여러개가 들어올 수 있다. graphQLErrors apollo docs에 나와있나
if (graphQLErrors !== undefined) {
for (const err of graphQLErrors) {
// 1-2 해당 에러가 토큰만료 에러인지 체크하기(UNAUTHENTICATED)
if (err.extensions.code === "UNAUTHENTICATED") {
return fromPromise(
// 2. refreshToken으로 accessToken을 재발급 받기 후 나주에 쓸 가능성이 높으므로 컴포넌트로 빼기
getAccessToken().then((newAccessToken) => {
setAccessToken(newAccessToken);
// 3. 재발급 받은 accessToken으로 방금 실패한 쿼리의 정보 수정하기
if (typeof newAccessToken !== "string") return;
operation.setContext({
headers: {
...operation.getContext().headers, // Authorization: Bearer asldkfjlasdk => 만료된 토큰이 추가되어 있는 상태
Authorization: `Bearer ${newAccessToken}`, // 3-1 토큰만 새걸로 바꿔치기
},
});
})
).flatMap(() => forward(operation)); // 3-3 방금 수정한 쿼리 재요청하기
}
}
}
});
const client = new ApolloClient({
link: ApolloLink.from([errorLink, uploadLink]),
cache: GLOBAL_STATE, // 컴퓨터의 메모리에다가 백엔드에서 받아온 데이터 모두 임시로 저장해 놓기
});
return (
<>
<ApolloProvider client={client}>{props.children}</ApolloProvider>
</>
);
}
// useAuth.tsx
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useRecoilValueLoadable } from "recoil";
import { restoreAccessTokenLoadable } from "../../../../commons/stores";
export const useAuth = () => {
const router = useRouter();
const refreshToken = useRecoilValueLoadable(restoreAccessTokenLoadable);
// 로그인 체크
useEffect(() => {
void refreshToken.toPromise().then((newAccessToken) => {
if (newAccessToken === undefined) {
alert("로그인 후 이용 가능합니다!!!");
void router.push("/signIn");
}
});
}, []);
};
// 가져올 컴포넌트 안에서
useAuth()
감사합니다.