[ 공모전 ] Auth : 토큰과 전역관리(axios 모듈화, 로그인여부 확인하기)

최문길·2024년 7월 27일
1

공모전

목록 보기
33/46

프로젝트에서 제일 중요한 지점중 하나는 auth관리라고 요번에 많이 느꼈다.
전반적인 기능개발(조건부 처리) , 페이지 단위에서 데이터 통신등 여러 조건들을 신경 쓰게 되는데,

요번 프로젝트에서

  • 어떻게 하면 누구나 같이 사용하기 편한지 그리고,
  • 로그인에 따른 조건부 처리를 어떻게 해야 할지

위 2가지를 고민하고 생각해봤다.

axios 모듈화

서버와 통신 할 때 axiosreact-query 를 사용한다.
각각의 page단에서 axios를 통해 request-header 에 토큰을 넣으면 되겠지만,
하나의 instance를 만들면 그 안에서 url과 method(get,post,put...)를 바꿔주면 되므로, 모듈화를 만들어야 겠다 판단했다.

// server_api.ts
import { I_AuthStore } from '@/types/auth/auth';
import axios, { InternalAxiosRequestConfig } from 'axios';

const baseURL = process.env.NEXT_PUBLIC_SERVER_BASE_URL;
const SESSION_ID = process.env.NEXT_PUBLIC_SESSION_PATH;

/**
 * @explain 서버와 요청할 때 토큰이 필요한 함수를 위한 util함수
 * @returns
 */
const getAuthorizationToken = () => {
  // zustand action으로 호출해줄걸...
  const AUTH_SESSION = sessionStorage.getItem(SESSION_ID as string);
  if (AUTH_SESSION) {
    const {
      state: { accessToken, refreshToken },
    }: { state: I_AuthStore } = JSON.parse(AUTH_SESSION);
    return {
      accessToken,
      refreshToken,
    };
  }
  throw Error('토큰이 없습니다. 로그인 해주시고 이용해주세요');
};

/**
 * @explain 서버에 요청을 보내기전에 세션 토큰을 헤더에 집어넣는 instance 입니다.
 */
export const axiosValidInstance = axios
  .create({
    baseURL,
  })
  .interceptors.request.use(
    (config: InternalAxiosRequestConfig) => {
      const { accessToken, refreshToken } = getAuthorizationToken();
      if (!accessToken || !refreshToken) return config;
      config.headers.Authorization = `Bearer ${accessToken}`;
      config.headers.refreshToken = refreshToken;
      return config;
    },
    error => {
      return Promise.reject(error);
    },
  );

실제 프로젝트에서 내가 작성한 파일을 가져왔다.
지금 보니 줄일 수 있는 것도 많은 것같네....

axios에서 제공하는 interceptor 를 이용하여 session에 토큰 값이 있는지를 판해서 프론트엔드 개발자들이 axios instance를 import 하여 사용 할 수 있게 만들었다.

로그인 여부확인을 어떻게 할까?

내가 직접적으로 client단에서 request header에 부착한 토큰으로 통신을 하는 경우는 없었다.

그러나, 로그인 여부를 각 페이지단에서 어떻게 처리를 할지도 맡게 되었다.
Next.js에서 제공하는 나중가서는 next-auth library , middleware를 물론 사용하여 로그인 여부에 따른 페이지 처리를 하였지만, 라이브러리와 미들웨어를 처리하기 이전에 고민 했던 생각을 작성하고 그에 따른 문제점이 무엇인지 생각하였다.

내 경우는 이 때까지, cookie 관련 지식자체가 없었기에... sessionStorage와 zustand의 전역 상태관리를 사용 할 때, 무엇을 사용하는 것이 좋을지 생각해봤다.

Layout

공통 레이아웃안에 router.pathname에따라 조건부 라우팅 해주기

onst Layout = ({ children }: { children: ReactNode }) => {
  const router = useRouter();
  const isLogin = useAuthStore((state) => state.isLogin);
  // 아니면 getValue를 통해 session을 구독하게 하여 isLogin값 가져오기
  const isLoginValue = getAuthSessionValue('isLogin')
  useEffect(() => {
    if (
      (!isLogin && router.pathname === Pages.home) ||
      router.pathname === Pages.zod
    ) {
      router.push("/auth-zustand");
    }
  }, [router.pathname, isLogin]);

음...
장점보단, router의 페이지마다 조건부를 추가해줘야하고, 로직이 너무 많아진다.
계속해서 호출과 조건에 따른 라우팅을 해야 하므로...
이 다음 useEffect와 별반 다를게 없어보인다.

useEffect & useLayoutEffect

페이지 컴포넌트에서 직접 로그인 여부 확인 방법 중 하나로 useEffect hook을 사용하여 컴포넌트 마운트 시 zustand의 store에 accessToken 혹은 isLogin 값을 가지고 redirect 처리하는 방법을 생각했다.

// SamplePage.tsx
const SamplePage = ()=> {
  const router = useRouter()
  useEffect(()=> {
    const isLogin = useAuthStore(state=>state.isLogin);
    if(!isLogin)router.push('/login')
  },[])
  return <Component/>
}

장점으로는

  • 구현이 간단하고, 직관적이다.

단점

  • 각 페이지 마다 사용해줘야 한다.
  • 새로고침 시 알수 없다. -> sessionStorage에는 true이지만, 값을 받아오는 함수 또한 각 페이지마다 작성해야 한다. 물론 _app.tsx에서 useEffect를 사용하여 먼저 getAuth같은 session값을 가져오는 것으로 처리 하면 되지만, 전역상태관리를 페이지단과 _app.tsx 두 군데를 관리해야한다.
// _app.tsx
export default function App() {
  const isLogin = useAuthStore((state) => state.isLogin); // 매번 호출됨
  useEffect(() => {
    const result = getAllAuthValue(); // 매번 호출됨
    if (result.isLogin && !isLogin) setAuthInSession(result); 
    // 아이디 바꿔서 로그인 하면 그에따른 예외 처리 추가해야함
  }, []);

HOC 컴포넌트

예전 프로젝트에서 react-error-boundary를 사용 했는데, 그때 HOC pattern을 사용하여 routing되는 컴포넌트를 감싸줬었다.

const withAuth = (WrappedComponent: React.ComponentType) => {
  const Component = () => {
    const isLogin = useAuthStore((state) => state.isLogin);
    const router = useRouter();

    useEffect(() => {
      if (!isLogin) router.push("/login");
      if (isLogin) router.push("/");
    }, [isLogin]);
    return <WrappedComponent />; // Component가 return하는 컴포넌트
  };
  return Component; // Component를 return 
};

export default withAuth;

// page.tsx
export default withAuth(about);
export default withAuth(Home);
//...wrapped pages of other needed

장점

  • 코드 재사용가능
  • 직관적이다.

단점

  • 코드가 당연히 복잡해질 수 있다.
  • 각 페이지마다 useEffect 하는 것과 사실 별반 차이가 없어보인다.

문제점

코드들이 직관적이고, 각 페이지를 개발 하는 사람들이 작성해주면 되지만,

  • 새로고침
  • 로그인하는 유저의 아이디가 바뀔 때
  • 페이지가 라우팅 할 때마다 무의미한 호출과 조건의 반복
  • 렌더링 이후에 작동하는 useEffect에 따른 처리(렌더링 되기전에 되었으면...)
  • 페이지 추가마다 작성해야할 코드들

위의 5가지만 봐도, 불편한 점이 많다.

마무리

새로고침시에도, 먼저 로그인 관련 여부를 확인 했으면 좋겠고
페이지를 추가 할 때마다 작성해야할 코드들을 단축 시켰으면 좋겠다.

auth는 중앙(진입점)에서 처리하고, 그 하위 또는 파생으로 페이지마다 조건부 렌더링이 되기를 바란다.
axios module화를 통해 하나의 instance를 가지고 각 페이지마다 처리하는 것과 맞췄으면 하는 바램으로 인한 것이기에

이때 나온 Next.js의 middleware.ts 파일인데, middleware를 사용하기 위해 먼저
Next.js와 호완성을 이루는 Next-Auth 라이브러리를 적용하게 된 계기와 라이브러리 사용법, 등을 포스팅 해보자

0개의 댓글

관련 채용 정보