본문 바로가기

개발

React에서의 Authorization

정답이 아니라 저는 이렇게 했다.. 정도를 소개하기 위한 글입니다. 개선할 점이나 의견 모두 환영합니다 🙂

Authorization

정의

Authorization은 직역하면 권한 부여 정도가 되는데, 인증(authentication)과는 조금 다른 개념이다. 로그인이 인증이라면, 사용자 (ex. 관리자 / 유저 / 게스트(로그인 x))에 따라 접근할 수 있는 페이지를 다르게 하는 것이 Authorization이라고 할 수 있다.

pseudo-code

어떤 페이지에 Authorization을 적용한 것을 pseudo-code로 표현하자면 이렇다.

 

const UserPage = () => {
  const 유저레벨 = 유저정보가져오기();

  if (유저레벨 === 게스트) {
    // 로그인 페이지
    return;
  }

  if (유저레벨 === 관리자) {
    // 관리자 전용 유저 페이지
    return;
  }

  // 일반 유저 페이지
  return;
}

어려운 이유

이 때 생기는 어려움은 뭘까? 내가 겪었던 것들을 나열하면 다음과 같다.

 

  1. 유저정보가져오기()는 주로 전역 상태 또는 사용자 정보를 요청하는 hook일 가능이 높다. 따라서 유저레벨이 아직 확인되지 않은 initial value로 컴포넌트가 마운트되는 경우가 생긴다.
    ex) 페이지 새로고침 / URL 직접 입력해서 접근할 경우

  2. 유저레벨에 따라 다른 화면을 보여주는 방식이 다양할 수 있다. 이를테면 컴포넌트를 리턴할 수도 있고, url을 변경하여 사용자를 강제로 이동시킬 수도 있다. 각각의 방법에는 문제가 뒤따른다.
    ex) 페이지 컴포넌트 리턴 -> 주소와 실제 보여지는 컴포넌트가 다름 / 강제 이동 -> UX 좋지 않음

  3. 위 로직을 권한이 필요한 페이지마다 추가해줘야 한다. 그리고 페이지마다 필요한 권한이 다를 수 있다.

이렇게 다양한 문제가 있어서인지 best practice를 찾기도 어렵다. 당장 구글링을 해봐도 다양한 방법이 나온다. 그래서 오히려(?) 자유롭게 부딪혀가며 구현을 했는데, 차근차근 소개해보려고 한다.

해결한 방법

1. 유저 정보 가져오기

유저가 초기화되었는지를 나타내는 변수를 하나 만들어 유저 정보와 묶어 관리한다. 상태 관리를 어떻게 하는지에 따라 세부 구현은 달라지겠지만, 핵심은 미확인 상태를 두는 것이다.

이 때는 유저가 게스트인지, 일반 회원인지, 관리자인지 아직 판단이 되지 않았으므로 화면상에도 로딩 중임을 표시해주는 것이 적절할 수 있다.

2. 화면을 보여주는 방식

업무에서는 nextjs를 사용하고 있어서 next/router로 상황에 따라 홈이나 로그인 페이지로 pushreplace를 해주었다. 그리고 사용자에게는 '권한이 없어서 이동한다'는 알림을 표시했다.

아예 권한이 없음을 보여주는 페이지를 따로 만들어 이동시킨 뒤, 해당 페이지에 홈이나 로그인 페이지로의 링크를 넣는 것도 대안이 될 수 있다.

3. 반복되는 로직

hoc 패턴을 사용해서 권한이 필요한 페이지에 간단히 적용할 수 있도록 했다.

a higher-order component is a function that takes a component and returns a new component

 

여기서는 컴포넌트를 인자로 받아서 검증 로직을 적용시켜 리턴한다. 컨벤션상 withAuthorization이라고 이름을 붙여보았다.

코드

단순화한 withAuthorization 코드이다.

 

import type { ComponentType } from "react";
import type { AccessLevel } from "src/types";
// 기타 import 생략

const withAuthorization = <P extends object>(
  WrappedComponent: ComponentType<P>, // 감쌀 컴포넌트
  pageAccessLevel: AccessLevel, // 그 외 파라미터들
) => {
  // 새로운 컴포넌트를 정의함
  const WithAuth = (props: P) => {
    // isInitialized: 유저 초기화 여부
    const { accessLevel, isInitialized } = useUser();
    const router = useRouter();

    useEffect(() => {
      if (!isInitialized) {
        // 유저 초기화(전역 상태 업데이트)
        return;
      }
    }, [isInitialized]);

    if (!isInitialized) { // 유저 미확인
      return null; // 또는 로딩 중 표시
    }

    if (accessLevel === 0) { // 게스트
      router.push("/login");
      return null;
    }

    if (accessLevel < pageAccessLevel) { // 권한 없음
      router.replace("/");
      return null;
    }

    return <WrappedComponent {...props} />;
  }

  // 위에서 인자로 받은 컴포넌트에 인증 로직을 적용했고,
  // 그 새로운 컴포넌트를 리턴한다.
  return WithAuth;
}

 

페이지 컴포넌트에서 이렇게 적용할 수 있다.

 

import { withAuthorization } from "src/hocs";
import { PageAccess } from "src/config";

// 마이페이지
const MyPage = (props: MyPageProps) => {
  // ...
}

export default withAuthorization(MyPage, PageAccess.User);

 

nextjs에서는 물론 그냥 react에서도 같은 방식으로 Authorization을 구현할 수 있다.