Как создать защищённый маршрут с помощью react-router-dom?

Вопрос или проблема

Как создать защищенный маршрут с помощью react-router-dom и сохранить ответ в localStorage, чтобы при следующем открытии пользователь мог снова увидеть свои данные. После входа в систему их следует перенаправить на страницу панели управления.

Вся функциональность добавлена в ContextApi.
Ссылка на Codesandbox: Код

Я пытался, но не смог этого добиться

Страница маршрута

import React, { useContext } from "react";
import { globalC } from "./context";
import { Route, Switch, BrowserRouter } from "react-router-dom";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";
import PageNotFound from "./PageNotFound";

function Routes() {
  const { authLogin } = useContext(globalC);
  console.log("authLogin", authLogin);

  return (
    <BrowserRouter>
      <Switch>
        {authLogin ? (
          <>
            <Route path="/dashboard" component={Dashboard} exact />
            <Route exact path="/About" component={About} />
          </>
        ) : (
          <Route path="/" component={Login} exact />
        )}

        <Route component={PageNotFound} />
      </Switch>
    </BrowserRouter>
  );
}
export default Routes;

Страница контекста

import React, { Component, createContext } from "react";
import axios from "axios";

export const globalC = createContext();

export class Gprov extends Component {
  state = {
    authLogin: null,
    authLoginerror: null
  };
  componentDidMount() {
    var localData = JSON.parse(localStorage.getItem("loginDetail"));
    if (localData) {
      this.setState({
        authLogin: localData
      });
    }
  }

  loginData = async () => {
    let payload = {
      token: "ctz43XoULrgv_0p1pvq7tA",
      data: {
        name: "nameFirst",
        email: "internetEmail",
        phone: "phoneHome",
        _repeat: 300
      }
    };
    await axios
      .post(`https://app.fakejson.com/q`, payload)
      .then((res) => {
        if (res.status === 200) {
          this.setState({
            authLogin: res.data
          });
          localStorage.setItem("loginDetail", JSON.stringify(res.data));
        }
      })
      .catch((err) =>
        this.setState({
          authLoginerror: err
        })
      );
  };
  render() {
    // console.log(localStorage.getItem("loginDetail"));
    return (
      <globalC.Provider
        value={{
          ...this.state,
          loginData: this.loginData
        }}
      >
        {this.props.children}
      </globalC.Provider>
    );
  }
}

Проблема

<BrowserRouter>
  <Switch>
    {authLogin ? (
      <>
        <Route path="/dashboard" component={Dashboard} exact />
        <Route exact path="/About" component={About} />
      </>
    ) : (
      <Route path="/" component={Login} exact />
    )}

    <Route component={PageNotFound} />
  </Switch>
</BrowserRouter>

Компонент Switch не обрабатывает отрисовку ничего, кроме компонентов Route и Redirect. Если вы хотите “вложить” так, то вам нужно обернуть каждый в общие маршруты, но это совершенно ненужно.

Ваш компонент входа также не обрабатывает перенаправление обратно на любую “домашнюю” страницу или частные маршруты, к которым первоначально был доступ.

Решение

react-router-dom v6

В версии 6 пользовательские компоненты маршрутов вышли из моды, предпочтительный метод – использовать компонент макета аутентификации.

import { Navigate, Outlet } from 'react-router-dom';

const PrivateRoutes = () => {
  const location = useLocation();
  const { authLogin } = useContext(globalC);

  if (authLogin === undefined) {
    return null; // или индикатор загрузки/спиннер/и т.д.
  }

  return authLogin 
    ? <Outlet />
    : <Navigate to="/login" replace state={{ from: location }} />;
}

<BrowserRouter>
  <Routes>
    <Route path="/" element={<PrivateRoutes />} >
      <Route path="dashboard" element={<Dashboard />} />
      <Route path="about" element={<About />} />
    </Route>
    <Route path="/login" element={<Login />} />
    <Route path="*" element={<PageNotFound />} />
  </Routes>
</BrowserRouter>

или

const routes = [
  {
    path: "/",
    element: <PrivateRoutes />,
    children: [
      {
        path: "dashboard",
        element: <Dashboard />,
      },
      {
        path: "about",
        element: <About />
      },
    ],
  },
  {
    path: "/login",
    element: <Login />,
  },
  {
    path: "*",
    element: <PageNotFound />
  },
];

export default function Login() {
  const location = useLocation();
  const navigate = useNavigate();
  const { authLogin, loginData } = useContext(globalC);

  useEffect(() => {
    if (authLogin) {
      const { from } = location.state || { from: { pathname: "/" } };
      navigate(from, { replace: true });
    }
  }, [authLogin, location, navigate]);

  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <button type="button" onClick={loginData} className="btn btn-primary">
        Вход
      </button>
    </div>
  );
}

react-router-dom v5

Создайте компонент PrivateRoute, который использует ваш контекст аутентификации.

const PrivateRoute = (props) => {
  const location = useLocation();
  const { authLogin } = useContext(globalC);

  if (authLogin === undefined) {
    return null; // или индикатор загрузки/спиннер/и т.д.
  }

  return authLogin ? (
    <Route {...props} />
  ) : (
    <Redirect
      to={{
        pathname: "/login",
        state: { from: location }
      }}
    />
  );
};

Обновите ваш компонент Login, чтобы обработать перенаправление обратно к исходному маршруту.

export default function Login() {
  const location = useLocation();
  const history = useHistory();
  const { authLogin, loginData } = useContext(globalC);

  useEffect(() => {
    if (authLogin) {
      const { from } = location.state || { from: { pathname: "/" } };
      history.replace(from);
    }
  }, [authLogin, history, location]);

  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <button type="button" onClick={loginData} className="btn btn-primary">
        Вход
      </button>
    </div>
  );
}

Отрисуйте все ваши маршруты в “плоском списке”

function Routes() {
  return (
    <BrowserRouter>
      <Switch>
        <PrivateRoute path="/dashboard" component={Dashboard} />
        <PrivateRoute path="/About" component={About} />
        <Route path="/login" component={Login} />
        <Route component={PageNotFound} />
      </Switch>
    </BrowserRouter>
  );
}

Для v6:

import { Routes, Route, Navigate } from "react-router-dom";

function App() {
  return (
    <Routes>
      <Route path="/public" element={<PublicPage />} />
      <Route
        path="/protected"
        element={
          <RequireAuth redirectTo="/login">
            <ProtectedPage />
          </RequireAuth>
        }
      />
    </Routes>
  );
}

function RequireAuth({ children, redirectTo }) {
  let isAuthenticated = getAuth();
  return isAuthenticated ? children : <Navigate to={redirectTo} />;
}

Ссылка на документацию:
https://gist.github.com/mjackson/d54b40a094277b7afdd6b81f51a0393f

Этот ответ для новых пользователей Reactjs

React Router Dom: ^ v6

//App.js
<Route path="/"
       element={<HomePage /> } />

<Route path="/dashboard"
       element={<AuthMiddleware> <DashboardPage /> </AuthMiddleware>}
/>
// AuthMiddelware.js
import { Navigate } from "react-router-dom"

export const AuthMiddleware = (props) => { 

    const token = true; // получить статус входа здесь

    const { auth=token, children, redirect="/login" } = props; 

    if (!auth) {
       return <Navigate to={redirect} replace />;
    }
    return children;
 };

Если вы хотите простое решение, используйте компонент входа в App.js, если пользователь вошел в систему, установите переменную пользователя. Если переменная пользователя установлена, то начинайте эти маршруты, иначе он останется на странице входа. Я реализовал это в своем проекте.

  return (
    <div>
      <Notification notification={notification} type={notificationType} />

      {
        user === null &&
        <LoginForm startLogin={handleLogin} />
      }

      {
        user !== null &&
        <NavBar user={user} setUser={setUser} />
      }

      {
        user !== null &&
        <Router>
          <Routes>
            <Route exact path="/" element={<Home />} />
            <Route exact path="/adduser" element={<AddUser />} /> />
            <Route exact path="/viewuser/:id" element={<ViewUser />} />
          </Routes>
        </Router>
      }

    </div>
  )

import { BrowserRouter as Router } from "react-router-dom";        
import { Routes, Route } from "react-router-dom";      
import Home from "./component/home/Home.js";      
import Products from "./component/Products/Products.js";      
import Profile from "./component/user/Profile.js";      

import ProtectedRoute from "./component/Route/ProtectedRoute";      
function App() {      
 return (      
    <Router>      
    <Routes>      
        <Route path="/" element={<Home />} />      
        <Route path="/products" element={<Products />} />      
        <Route      
          path="/account"      
          element={      
            <ProtectedRoute  >      
              <Profile />      
            </ProtectedRoute>      
          }      
        />      
      </Routes>      
       </Router>      
  );      
}      

//ProtectedRoute       

export default App;      
import React from "react";      
import { useSelector } from "react-redux";      
import { Navigate } from "react-router-dom";      

function ProtectedRoute({ children }) {      
  const { isAuthecated, loading } = useSelector((state) => state.user);      

  if (!isAuthecated) {      
    return <Navigate to="/login" replace />;      
  }      
  return children;      
}      
export default ProtectedRoute;      

import { v4 as uuidv4 } from "uuid";

const routes = [
    {
        id: uuidv4(),
        isProtected: false,
        exact: true,
        path: "/home",
        component: param => <Overview {...param} />,
    },
    {
        id: uuidv4(),
        isProtected: true,
        exact: true,
        path: "/protected",
        component: param => <Overview {...param} />,
        allowed: [...advanceProducts], // подписка
    },
    {
        // если вы используете условный рендеринг для того же пути
        id: uuidv4(),
        isProtected: true,
        exact: true,
        path: "https://stackoverflow.com/",
        component: null,
        conditionalComponent: true,
        allowed: {
            [subscription1]: param => <Overview {...param} />,
            [subscription2]: param => <Customers {...param} />,
        },
    },
]

// Компонент навигации
import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Switch, Route, useLocation } from "react-router-dom";

// ...логика компонента
<Switch>
    {routes.map(params => {
        return (
            <ProtectedRoutes
                exact
                routeParams={params}
                key={params.path}
                path={params.path}
            />
        );
    })}
    <Route
        render={() => {
            props.setHideNav(true);
            setHideHeader(true);
            return <ErrorPage type={404} />;
        }}
    />
</Switch>

// Компонент ProtectedRoute
import React from "react";
import { Route } from "react-router-dom";
import { useSelector } from "react-redux";

const ProtectedRoutes = props => {
    const { routeParams } = props;
    const currentSubscription = 'xyz'; // ваша текущая подписка;
    if (routeParams.conditionalComponent) {
        return (
            <Route
                key={routeParams.path}
                path={routeParams.path}
                render={routeParams.allowed[currentSubscription]}
            />
        );
    }
    if (routeParams.isProtected && routeParams.allowed.includes(currentSubscription)) {
        return (
            <Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
        );
    }
    if (!routeParams.isProtected) {
        return (
            <Route key={routeParams.path} path={routeParams.path} render={routeParams?.component} />
        );
    }
    return null;
};

export default ProtectedRoutes;

Хотелось бы отметить, что никогда не забывайте передавать путь как пропс в ProtectedRoute, иначе он не будет работать.

</div><div class="s-prose js-post-body" itemprop="text">

Вот простой защищенный маршрут react-router v6. Я поместил все маршруты, которые хочу защитить, в routes.js:-

const routes = [{ path: "/dasboard", name:"Dashboard", element: <Dashboard/> }]

Чтобы отрендерить маршруты, просто отображайте их следующим образом: –

<Routes>
   {routes.map((routes, id) => {
     return(
     <Route
         key={id}
         path={route.path}
         exact={route.exact}
         name={route.name}
         element={
            localStorage.getItem("token") ? (
                route.element
             ) : (
                <Navigate to="/login" />
             )
         }
     )
   })
  }
</Routes>
</div>

Ответ или решение

Создание защищенного маршрута в приложении React с использованием react-router-dom является важной частью разработки для обеспечения безопасности пользовательских данных. В данном ответе мы рассмотрим, как это реализовать, сохраняя информацию о пользователе в localStorage, чтобы они могли видеть свои данные при повторном входе, а также автоматическую переадресацию на страницу панели управления после успешной аутентификации. Все функции будут реализованы в контексте Context API.

Шаг 1: Настройка контекста

Сначала создадим контекст для аутентификации. Этот контекст будет управлять состоянием аутентификации и хранить данные пользователя в localStorage.

import React, { Component, createContext } from "react";
import axios from "axios";

export const AuthContext = createContext();

export class AuthProvider extends Component {
    state = {
        authLogin: null,
        authLoginError: null
    };

    componentDidMount() {
        const localData = JSON.parse(localStorage.getItem("loginDetail"));
        if (localData) {
            this.setState({ authLogin: localData });
        }
    }

    loginData = async () => {
        // Пример загрузки данных для аутентификации
        const payload = {
            token: "ctz43XoULrgv_0p1pvq7tA",
            data: {
                name: "nameFirst",
                email: "internetEmail",
                phone: "phoneHome"
            }
        };
        try {
            const res = await axios.post(`https://app.fakejson.com/q`, payload);
            if (res.status === 200) {
                this.setState({ authLogin: res.data });
                localStorage.setItem("loginDetail", JSON.stringify(res.data));
            }
        } catch (error) {
            this.setState({ authLoginError: error });
        }
    };

    render() {
        return (
            <AuthContext.Provider value={{
                ...this.state,
                loginData: this.loginData
            }}>
                {this.props.children}
            </AuthContext.Provider>
        );
    }
}

Шаг 2: Создание защищенного маршрута

Теперь мы создадим компонент защищенного маршрута, который будет проверять, авторизован ли пользователь, и перенаправлять его на страницу входа, если нет.

import React, { useContext } from "react";
import { Navigate } from "react-router-dom";
import { AuthContext } from "./context";

const ProtectedRoute = ({ children }) => {
    const { authLogin } = useContext(AuthContext);

    if (authLogin === null) {
        return null; // или индикатор загрузки
    }

    return authLogin ? children : <Navigate to="/login" replace />;
};

export default ProtectedRoute;

Шаг 3: Настройка маршрутов

Теперь мы настроим маршруты в нашем приложении. Мы будем использовать Switch или Routes (в зависимости от версии react-router-dom, которую вы используете) и добавим защищенные маршруты.

import React, { useContext } from "react";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import { AuthContext } from "./context";
import About from "./About";
import Dashboard from "./Dashboard";
import Login from "./Login";

function AppRoutes() {
    const { authLogin } = useContext(AuthContext);

    return (
        <Router>
            <Routes>
                <Route path="/login" element={<Login />} />
                <Route path="/" element={<ProtectedRoute><Dashboard /></ProtectedRoute>} />
                <Route path="/about" element={<ProtectedRoute><About /></ProtectedRoute>} />
                {/* Добавьте дополнительные защищенные маршруты здесь */}
            </Routes>
        </Router>
    );
}

export default AppRoutes;

Шаг 4: Компонент входа

В компоненте Login мы будем управлять процессом входа пользователя и перенаправлением его на нужную страницу после успешной аутентификации.

import React, { useEffect, useContext } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { AuthContext } from "./context";

const Login = () => {
    const navigate = useNavigate();
    const location = useLocation();
    const { authLogin, loginData } = useContext(AuthContext);

    useEffect(() => {
        if (authLogin) {
            const { from } = location.state || { from: { pathname: "/" } };
            navigate(from, { replace: true });
        }
    }, [authLogin, location, navigate]);

    return (
        <div style={{ height: "100vh" }} className="d-flex justify-content-center align-items-center">
            <button type="button" onClick={loginData} className="btn btn-primary">
                Войти
            </button>
        </div>
    );
};

export default Login;

Заключение

Теперь ваше приложение имеет защищенные маршруты, и пользователи могут войти в систему и видеть свои данные на соответствующих страницах. Убедитесь, что вы импортировали все необходимые компоненты и добавили их в ваше главное приложение.

Этот подход позволяет эффективно управлять аутентификацией пользователей и защищать маршруты, используя Context API и react-router-dom. Не забывайте также тестировать функциональность, чтобы убедиться, что все работает как задумано.

Оцените материал
Добавить комментарий

Капча загружается...