Home React Router v6에서 권한처리
Post
Cancel

React Router v6에서 권한처리

기본 설정

/src/hooks/useLocalStorage.js

로그인 정보를 저장하기 위한 유틸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { useState } from "react";

export const useLocalStorage = (keyName, defaultValue) => {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const value = window.localStorage.getItem(keyName);

      if (value) {
        return JSON.parse(value);
      } else {
        window.localStorage.setItem(keyName, JSON.stringify(defaultValue));
        return defaultValue;
      }
    } catch (err) {
      return defaultValue;
    }
  });
  const setValue = (newValue) => {
    try {
      window.localStorage.setItem(keyName, JSON.stringify(newValue));
    } catch (err) {
      console.log(err);
    }
    setStoredValue(newValue);
  };
  return [storedValue, setValue];
};

/src/context/AuthContext.jsx

Context를 이용하여 AuthProvider 하위의 컴포넌트에서 로그인, 로그아웃, 사용자 정보에 접근할 수 있게 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React, { createContext, useContext, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "../hooks/useLocalStorage";

const AuthContext = createContext();

export const AuthProvider = ({ children }) => {
  const [user, setUser] = useLocalStorage("user", null);
  const navigate = useNavigate();

  /**
   * 로그인 처리 후 메인화면으로 이동
   * @param {any} data
   */
  const login = async (data) => {
    setUser(data);
    navigate("/");
  };

  /**
   * 로그아웃 처리 후 로그인 화면으로 이동
   */
  const logout = () => {
    setUser(null);
    navigate("/login", { replace: true });
  };

  /**
   * 로그인 여부 조회
   * @returns true: 로그인상태, false: 로그아웃상태
   */
  const isLogin = () => {
    return !!user;
  };

  const value = useMemo(
    () => ({
      user,
      login,
      logout,
      isLogin,
    }),
    [user]
  );
  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};

export const useAuth = () => {
  return useContext(AuthContext);
};

/src/components/layout/auth/AuthLayout.jsx

AuthProvider를 감싸서 하위의 컴포넌트들이 권한 정보의 접근이 가능하게 함.

1
2
3
4
5
6
7
8
9
10
import React from "react";
import { useOutlet } from "react-router-dom";
import { AuthProvider } from "../../context/AuthContext";

const AuthLayout = () => {
  const outlet = useOutlet();
  return <AuthProvider>{outlet}</AuthProvider>;
};

export default AuthLayout;

/src/components/auth/ProtectedLayout.jsx

전체 layout을 구성하고 로그인 체크를 한다. 로그인이 필요한 모든 화면은 해당 컴포넌트의 자식으로 등록한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from "react";
import { Navigate, Outlet } from "react-router-dom";

import { useAuth } from "../../context/AuthContext";
import Header from "../layout/Header";
import Nav from "../layout/Nav";
import Footer from "../layout/Footer";

export default function ProtectedLayout() {
  const { isLogin } = useAuth();

  // 로그인체크
  if (!isLogin()) {
    // 로그인 화면으로 이동
    return <Navigate to="/login" />;
  }

  return (
    <>
      <div className="wrap">
        <Header />
        <main className="main">
          <Nav />
          <Outlet />
        </main>
      </div>
      <Footer />
    </>
  );
}

그 밖에 화면들

/src/pages/LoginPage.jsx

로그인화면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { useState } from "react";
import { useAuth } from "../context/AuthContext";

export default function LoginPage() {
  const [email, setEmail] = useState("");
  const { login } = useAuth();
  const onClickLogin = () => {
    login({
      email,
    });
  };

  return (
    <div>
      <h3>로그인화면</h3>
      <div>
        email:
        <input value={email} onChange={(e) => setEmail(e.target.value)} />
        <button onClick={onClickLogin}>로그인</button>
      </div>
    </div>
  );
}

/src/pages/MainPage.jsx

1
2
3
4
5
import React from "react";

export default function MainPage() {
  return <div>MainPage</div>;
}

/src/pages/error/PageNotFound.jsx

1
2
3
4
5
import React from "react";

export default function PageNotFound() {
  return <div>PageNotFound</div>;
}

/router/system.js

각 메뉴별로 모듈화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from "react";
import AuthManagePage from "../pages/system/AuthManagePage";
import RootMenuMngPage from "../pages/system/RootMenuMngPage";

const system = [
  {
    path: "system/auth",
    element: <AuthManagePage />,
  },
  {
    path: "system/menu",
    element: <RootMenuMngPage />,
  },
];

export default system;

/src/App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import React from "react";
import "./App.css";
import { Route, Routes } from "react-router-dom";

import AuthLayout from "./components/auth/AuthLayout";

import LoginPage from "./pages/account/LoginPage";
import MainPage from "./pages/MainPage";
import ProtectedLayout from "./components/auth/ProtectedLayout";
import PageNotFound from "./pages/error/PageNotFound";

import system from "./router/system";

export default function App() {
  return (
    <Routes>
      <Route path="*" element={<PageNotFound />} />

      <Route element={<AuthLayout />}>
        <Route path="/login" element={<LoginPage />} />

        <Route element={<ProtectedLayout />}>
          <Route path="/" element={<MainPage />} />
          {system.map((item) => (
            <Route key={item.path} path={item.path} element={item.element} />
          ))}
        </Route>
      </Route>
    </Routes>
  );
}

테스트

로그인

로그인 이메일을 입력하고 로그인버튼 클릭

로그인 후 메인화면

메인화면 로그인 처리후 Root화면으로 이동

Page Not Found

PageNotFound

출처: https://blog.logrocket.com/complete-guide-authentication-with-react-router-v6

This post is licensed under CC BY 4.0 by the author.