요약

example/auth에 있는 내용을 참고하여 구현했습니다.

 

https://github.dev/remix-run/react-router

Setting up your web editor eyJ3b3JrYmVuY2hUeXBlIjoiZWRpdG9yIiwid29ya2JlbmNoQ29uZmlnIjp7InZzY29kZVZlcnNpb25JbmZvIjp7InN0YWJsZSI6eyJjb21taXQiOiI5N2RlYzE3MmQzMjU2ZjhjYTRiZmIyMTQzZjNmNzZiNTAzY2EwNTM0IiwicHJvZHVjdFZlcnNpb24iOiIxLjc0LjMifSwiaW5zaWRlciI6eyJjb21ta

github.dev

 

 

기존의 코드

useEffect를 활용하여 로컬스토리지 토큰을 검사했다, 이때 발생한 문제점이 Home 컴포넌트에서는 로그인을해야 데이터를 불러올 수 있는데 만약 로그인을 하지 않은 유저가 접근했다면 토큰이 없다는 401에러가 뜬 뒤에야 로그인 페이지로 이동한다.

 

이는 비용적으로, 보안적으로 문제가 될 수 있었기 때문에 수정이 필요하다고 생각했고 react-router 깃헙의 example을 통해 AuthProvider를 만들어 루트를 보호해줄 수 있었다.

const Router = () => {
  const location = useLocation();

  React.useEffect(() => {
    const token = localStorage.getItem("token");
    if (token === null && location.pathname !== "/auth")
      history.replace("/auth");
  }, [localStorage.getItem("token")]);

  return (
    <Routes>
      <Route path="" element={<Home />} />
      <Route path="/auth" element={<Auth />} />
      <Route path="/auth/signup" element={<SignUp />} />
    </Routes>
  );
};

export default Router;

 

해결한 코드

React context API와 react-query의 useMutation을 활용했다. 따라서 커스텀이 필요하다면 공식문서나 구글링을 참고하는게 좋아보인다.

 

ContextAPI의 Provider를 정의해 토큰이 있는지, 로그인/로그아웃 상태와 상태로직을 전달할 수 있도록 했고 이후 Route를 provider로 감싸줬다

// hooks/AuthProvider.tsx
import React from "react";
import { To, useNavigate } from "react-router-dom";
import { AuthContextType } from "../../types/auth";
import { SignFormType } from "../../types/form";
import { findToken, removeToken, storageToken } from "../../utils/handleToken";
import useSignin from "../queries/Auth/useSignin";
import useSignUp from "../queries/Auth/useSignUp";

export const AuthContext = React.createContext<AuthContextType>(null!);

export function useAuth() {
  return React.useContext(AuthContext);
}

function AuthProvider({ children }: { children: React.ReactNode }) {
  const [isToken, setIsToken] = React.useState(findToken());
  const SignInMutate = useSignin();
  const SignUpMutate = useSignUp();
  const navigate = useNavigate();

  function SignIn(callback?: VoidFunction) {
    return function (SignData: SignFormType) {
      SignInMutate(SignData, {
        onSuccess(response) {
          storageToken(response.token);
          setIsToken(true);
          if (callback) callback();
        },
      });
    };
  }

  function SignUp(callback?: VoidFunction) {
    return function (SignData: SignFormType) {
      SignUpMutate(SignData, {
        onSuccess(response) {
          storageToken(response.token);
          setIsToken(true);
          if (callback) callback();
        },
      });
    };
  }

  function LogOut(callback?: VoidFunction) {
    return function () {
      removeToken();
      setIsToken(false);
      if (callback) callback();
    };
  }

  function SignNavigateCallback(route: To) {
    return function () {
      navigate(route, { replace: true });
    };
  }

  const value = {
    token: isToken,
    SignIn,
    SignUp,
    LogOut,
    SignNavigateCallback,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export default AuthProvider;
const Router = () => {
  return (
    <AuthProvider>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/auth" element={<Auth />} />
        <Route path="/auth/signup" element={<SignUp />} />
        <Route path="*" element={<>NULL</>} />
      </Routes>
    </AuthProvider>
  );
};

 

이후 RequireAuth를 통해 만약 토큰이 없다면 로그인 페이지로 이동하고 토큰이 있다면 해당 컴포넌트를 렌더링하는 고차 컴포넌트를 만들어서 감싸주면 끝이 난다

// RequireAuth.tsx
import React from "react";
import { Navigate, useLocation } from "react-router-dom";
import { useAuth } from "./AuthProvider";

function RequireAuth({ children }: { children: JSX.Element }) {
  const auth = useAuth();
  const location = useLocation();

  if (!auth.token)
    return <Navigate to={"/auth"} state={{ from: location }} replace />;

  return children;
}

export default RequireAuth;
const Router = () => {
  return (
    <AuthProvider>
      <Routes>
        <Route
          index
          element={
            <RequireAuth>
              <Home />
            </RequireAuth>
          }
        />
        <Route path="/auth" element={<Auth />} />
        <Route path="/auth/signup" element={<SignUp />} />
        <Route path="*" element={<>NULL</>} />
      </Routes>
    </AuthProvider>
  );
};

export default Router;

 

회고

기능을 구현했을 때 원치않는 동작이 발생한다면 문제로써 정의해야 하는지를 잘 생각해봐야겠다. 몇번 해봤다고 생각해서 대수롭지 않게 넘겼다..

 

그리고 최근에 과제를 구현한다고 알고리즘 문제를 푼지 꽤 지났는데 다시 꾸준하게 하루에 2문제씩이라도 풀어야겠다

복사했습니다!