권한분기

남예지·2022년 12월 22일
0
post-thumbnail

권한분기

로그인 인증 이후에는 로그인 상황에 따른 권한 분기를 하게 되는데 예를 들어 로그인을 한 사람과 로그인을 하지 않은 사람, 운영자로 로그인한 사람, 판매자로 로그인한 사람, 거래처사장님으로 로그인한 사람 등 다양하게 권한을 분리한다.

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()

감사합니다.

profile
총총

0개의 댓글