احراز هویت با نسخه ۶ React Router

۶ React Router یک کتابخانه routing محبوب و قدرتمند برای برنامه‌های React است. کتابخانه React Router یک رویکرد declarative و مبتنی بر کامپوننت برای مسیریابی ارائه می‌دهد و وظایف رایج مربوط به رسیدگی به پارامترهای URL، redirectها و بارگذاری داده‌ها را انجام می‌دهد.

React Router یکی از بصری‌ترین APIهای موجود را ارائه می‌دهد و lazy loading و رندر سمت سرور سازگار با SEO را امکان‌پذیر می‌کند.

در این قاله قصد داریم تا نحوه ایجاد مسیرهای محافظت شده و احراز هویت با نسخه ۶ React Router را باهم بررسی کنیم.

شروع کار با React Router

برای شروع، ترمینال را باز می‌کنیم، با اجرای دستور زیر و با استفاده از vite یک پروژه React جدید می‌سازیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm create vite@latest ReactRouterAuthDemo -- --template react
cd ReactRouterAuthDemo
npm create vite@latest ReactRouterAuthDemo -- --template react cd ReactRouterAuthDemo
npm create vite@latest ReactRouterAuthDemo -- --template react
cd ReactRouterAuthDemo

سپس، React Router را به عنوان یک dependency در برنامه React نصب می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm install react-router-dom
npm install react-router-dom
npm install react-router-dom

هنگامی که React Router نصب شد، باید فایل

src/main.js
src/main.js را ویرایش کنیم.

BrowserRouter
BrowserRouter را از
react-router-dom
react-router-dom import می‌کنیم و سپس کامپوننت
<App />
<App /> را داخل
<BrowserRouter />
<BrowserRouter /> قرار می‌دهیم، به این صورت که:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import { BrowserRouter } from "react-router-dom";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App.jsx"; import "./index.css"; import { BrowserRouter } from "react-router-dom"; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> <BrowserRouter> <App /> </BrowserRouter> </React.StrictMode> );
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";

import { BrowserRouter } from "react-router-dom";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

اکنون، تنظیماتی که انجام دادیم به گونه‌ای است که می‌توانیم از کامپوننت‌های React Router و Hooks در هر نقطه از برنامه خود استفاده کنیم.

حال قصد داریم تا boilerplate code از فایل

App.js
App.js را با برخی از مسیرها جایگزین نماییم.

مسیریابی بیسیک با استفاده از React Router

React Router کامپوننت‌های

<Routes />
<Routes /> و
<Route />
<Route /> را فراهم می‌کند که ما را قادر می‌سازد تا کامپوننت‌ها را بر اساس مکان فعلی آن‌ها رندر کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// src/App.jsx
import { Routes, Route } from "react-router-dom";
import { LoginPage } from "./pages/Login";
import { HomePage } from "./pages/Home";
import "./App.css";
function App() {
return (
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
</Routes>
);
}
export default App;
// src/App.jsx import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import "./App.css"; function App() { return ( <Routes> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Routes> ); } export default App;
// src/App.jsx

import { Routes, Route } from "react-router-dom";
import { LoginPage } from "./pages/Login";
import { HomePage } from "./pages/Home";

import "./App.css";

function App() {
  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
      <Route path="/login" element={<LoginPage />} />
    </Routes>
  );
}

export default App;

مسیریابی بیسیک با استفاده از
<Route >
<Route >

<Route />
<Route /> mapping بین pathهای موجود در برنامه و کامپوننت‌های مختلف React را ارائه می‌دهد. به عنوان مثال، هنگامی که شخصی به مسیر
/login
/login هدایت می‌شود، برای این که کامپوننت
LoginPage
LoginPage را رندر کنیم فقط باید
<Route />
<Route /> را ارائه دهیم، مانند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
<Route path="/login" element={<LoginPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/login" element={<LoginPage />} />

کامپوننت

<Route />
<Route /> را می‌توانیم مانند یک عبارت if در نظر بگیریم. به این ترتیب، فقط در صورتی که با path مشخص شده مطابقت داشته باشد، بر روی یک لوکیشین URL با element خود عمل می‌کند.

مسیریابی بیسیک با استفاده از
<Routes />
<Routes />

کامپوننت

<Routes />
<Routes /> جایگزینی برای کامپوننت
<Switch />
<Switch /> از نسخه ۵ React Router است. برای استفاده از
<Routes />
<Routes />، ابتدا فایل‌های
Login.jsx
Login.jsx و
Home.jsx
Home.jsx را در دایرکتوری pages با محتوای زیر ایجاد می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// Login.jsx
export const LoginPage = () => (
<div>
<h1>This is the Login Page</h1>
</div>
);
// Home.jsx
export const HomePage = () => (
<div>
<h1>This is the Home Page</h1>
</div>
);
// Login.jsx export const LoginPage = () => ( <div> <h1>This is the Login Page</h1> </div> ); // Home.jsx export const HomePage = () => ( <div> <h1>This is the Home Page</h1> </div> );
// Login.jsx
export const LoginPage = () => (
  <div>
    <h1>This is the Login Page</h1>
  </div>
);

// Home.jsx
export const HomePage = () => (
  <div>
    <h1>This is the Home Page</h1>
  </div>
);

در مرحله بعد، دستور زیر را برای شروع برنامه اجرا می‌نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
npm run dev
npm run dev
npm run dev

در مرورگر، به طور پیش‌فرض کامپوننت

HomePage
HomePage را می‌بینیم. اگر به مسیر
/login
/login برویم، کامپوننت
LoginPage
LoginPage را در صفحه نمایش مشاهده خواهیم کرد. از طرف دیگر، می‌توانیم از یک آبجکت جاوااسکریپت ساده برای نشان دادن مسیرها در برنامه خود با استفاده از هوک
useRoutes
useRoutes استفاده نماییم. این یک رویکرد فانکشنال برای تعریف مسیرها است و مانند ترکیب کامپوننت‌های
<Routes />
<Routes /> و
<Route />
<Route /> کار می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// src/App.jsx
import { useRoutes } from "react-router-dom";
// ...
export default function App() {
const routes = useRoutes([
{
path: "/",
element: <HomePage />
},
{
path: "/login",
element: <LoginPage />
}
]);
return routes;
}
// src/App.jsx import { useRoutes } from "react-router-dom"; // ... export default function App() { const routes = useRoutes([ { path: "/", element: <HomePage /> }, { path: "/login", element: <LoginPage /> } ]); return routes; }
// src/App.jsx

import { useRoutes } from "react-router-dom";
// ...

export default function App() {
  const routes = useRoutes([
    {
      path: "/",
      element: <HomePage />
    },
    {
      path: "/login",
      element: <LoginPage />
    }
  ]);
  return routes;
}

اکنون که تنظیمات اولیه تکمیل شده است، قصد داریم به این موضوع بپردازیم که چگونه می‌توانیم مسیرهای محافظت شده ایجاد کنیم تا کاربران احراز هویت نشده نتوانند به محتوای خاصی در برنامه دسترسی داشته باشند.

ساخت مسیرهای محافظت شده

مسیرهای محافظت شده، که اغلب به عنوان مسیرهای private شناخته می‌شوند، یک مفهوم اساسی در توسعه وب است که برای محدود کردن دسترسی به صفحات یا منابع خاص فقط برای کاربران تأیید شده مورد استفاده قرار می‌گیرد.

برای پیاده‌سازی یک مسیر محافظت‌شده در پروژه، کار خود را با ایجاد یک هوک سفارشی با نام

useAuth
useAuth برای مدیریت state کاربر احراز هویت شده با استفاده از Context API و هوک
useContext
useContext شروع می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// src/hooks/useAuth.jsx
import { createContext, useContext, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "./useLocalStorage";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useLocalStorage("user", null);
const navigate = useNavigate();
// call this function when you want to authenticate the user
const login = async (data) => {
setUser(data);
navigate("/profile");
};
// call this function to sign out logged in user
const logout = () => {
setUser(null);
navigate("/", { replace: true });
};
const value = useMemo(
() => ({
user,
login,
logout,
}),
[user]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
// src/hooks/useAuth.jsx import { createContext, useContext, useMemo } from "react"; import { useNavigate } from "react-router-dom"; import { useLocalStorage } from "./useLocalStorage"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useLocalStorage("user", null); const navigate = useNavigate(); // call this function when you want to authenticate the user const login = async (data) => { setUser(data); navigate("/profile"); }; // call this function to sign out logged in user const logout = () => { setUser(null); navigate("/", { replace: true }); }; const value = useMemo( () => ({ user, login, logout, }), [user] ); return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }; export const useAuth = () => { return useContext(AuthContext); };
// src/hooks/useAuth.jsx

import { createContext, useContext, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "./useLocalStorage";
const AuthContext = createContext();

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

  // call this function when you want to authenticate the user
  const login = async (data) => {
    setUser(data);
    navigate("/profile");
  };

  // call this function to sign out logged in user
  const logout = () => {
    setUser(null);
    navigate("/", { replace: true });
  };

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

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

این هوک

useAuth
useAuth state و متدهای user را برای ورود و خروج کاربران نشان می‌دهد. هنگامی که کاربران با موفقیت وارد سیستم می‌شوند، متد
login()
login() state آن‌ها را تغییر می‌دهد تا وضعیت احراز هویت آن‌ها را منعکس کند. علاوه بر این، هنگامی که کاربران از سیستم خارج می‌شوند، با استفاده از هوک
useNavigate
useNavigate در React Router، آن‌ها را به صفحه اصلی هدایت می‌کنیم.

برای این که بتوانیم state کاربر را حتی پس از به‌روزرسانی صفحه حفظ نماییم، هوک

useLocalStorage
useLocalStorage را ایجاد می‌کنیم که مقدار state را با local storage مرورگر همگام‌سازی می‌نماید:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// src/hooks/useLocalStorage.jsx
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/hooks/useLocalStorage.jsx 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/hooks/useLocalStorage.jsx

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];
};

در مرحله بعد، کامپوننت

ProtectedRoute
ProtectedRoute را می‌سازیم، که state کاربر فعلی را از هوک
useAuth
useAuth بررسی می‌کند و در صورت عدم احراز هویت، آن‌ها را به صفحه اصلی هدایت می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Navigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
export const ProtectedRoute = ({ children }) => {
const { user } = useAuth();
if (!user) {
// user is not authenticated
return <Navigate to="/login" />;
}
return children;
};
import { Navigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedRoute = ({ children }) => { const { user } = useAuth(); if (!user) { // user is not authenticated return <Navigate to="/login" />; } return children; };
import { Navigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";

export const ProtectedRoute = ({ children }) => {
  const { user } = useAuth();
  if (!user) {
    // user is not authenticated
    return <Navigate to="/login" />;
  }
  return children;
};

در کد بالا، ما از کامپوننت

<Navigate />
<Navigate /> React Router برای هدایت کاربران احراز هویت نشده به مسیر
/login
/login استفاده می‌نماییم.

اکنون، گام بعدی اضافه کردن یک مسیر

LoginPage
LoginPage برای احراز هویت کاربر و یک مسیر
Secret
Secret است که فقط برای کاربران وارد شده قابل مشاهده می‌باشد.

برای این کار، یک فایل به نام

Login.jsx
Login.jsx در دایرکتوری pages خود ایجاد می‌کنیم و کد زیر را در آن قرار می‌دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useState } from "react";
import { useAuth } from "../hooks/useAuth";
export const LoginPage = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const { login } = useAuth();
const handleLogin = async (e) => {
e.preventDefault();
// Here you would usually send a request to your backend to authenticate the user
// For the sake of this example, we're using a mock authentication
if (username === "user" && password === "password") {
// Replace with actual authentication logic
await login({ username });
} else {
alert("Invalid username or password");
}
};
return (
<div>
<form onSubmit={handleLogin}>
<div>
<label htmlFor="username">Username:</label>
<input
id="username"
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label htmlFor="password">Password:</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit">Login</button>
</form>
</div>
);
};
import { useState } from "react"; import { useAuth } from "../hooks/useAuth"; export const LoginPage = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const { login } = useAuth(); const handleLogin = async (e) => { e.preventDefault(); // Here you would usually send a request to your backend to authenticate the user // For the sake of this example, we're using a mock authentication if (username === "user" && password === "password") { // Replace with actual authentication logic await login({ username }); } else { alert("Invalid username or password"); } }; return ( <div> <form onSubmit={handleLogin}> <div> <label htmlFor="username">Username:</label> <input id="username" type="text" value={username} onChange={(e) => setUsername(e.target.value)} /> </div> <div> <label htmlFor="password">Password:</label> <input id="password" type="password" value={password} onChange={(e) => setPassword(e.target.value)} /> </div> <button type="submit">Login</button> </form> </div> ); };
import { useState } from "react";
import { useAuth } from "../hooks/useAuth";
export const LoginPage = () => {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const { login } = useAuth();
  const handleLogin = async (e) => {
    e.preventDefault();
    // Here you would usually send a request to your backend to authenticate the user
    // For the sake of this example, we're using a mock authentication
    if (username === "user" && password === "password") {
      // Replace with actual authentication logic
      await login({ username });
    } else {
      alert("Invalid username or password");
    }
  };
  return (
    <div>
      <form onSubmit={handleLogin}>
        <div>
          <label htmlFor="username">Username:</label>
          <input
            id="username"
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
        </div>
        <div>
          <label htmlFor="password">Password:</label>
          <input
            id="password"
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
          />
        </div>
        <button type="submit">Login</button>
      </form>
    </div>
  );
};

این کامپوننت به عنوان interface ورود به سیستم عمل می‌کند و از هوک

useAuth
useAuth برای کنترل احراز هویت کاربر استفاده می‌نماید. هنگامی که کاربران اطلاعات خود را وارد کرده و فرم را ارسال می‌کنند، تابع
login()
login() از
useAuth
useAuth برای احراز هویت و ورود آن‌ها فراخوانی می‌شود.

به طور مشابه، یک فایل

Secret.jsx
Secret.jsx در دایرکتوری pages ایجاد می‌کنیم که یک صفحه ایمن را نشان می‌دهد و محتوا آن منحصراً برای کاربران تأیید شده نمایش داده می‌شود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useAuth } from "../hooks/useAuth";
export const Secret = () => {
const { logout } = useAuth();
const handleLogout = () => {
logout();
};
return (
<div>
<h1>This is a Secret page</h1>
<button onClick={handleLogout}>Logout</button>
</div>
);
};
import { useAuth } from "../hooks/useAuth"; export const Secret = () => { const { logout } = useAuth(); const handleLogout = () => { logout(); }; return ( <div> <h1>This is a Secret page</h1> <button onClick={handleLogout}>Logout</button> </div> ); };
import { useAuth } from "../hooks/useAuth";

export const Secret = () => {
  const { logout } = useAuth();

  const handleLogout = () => {
    logout();
  };

  return (
    <div>
      <h1>This is a Secret page</h1>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
};

همچنین یک دکمه خروج در صفحه

Secret
Secret در بالا اضافه قرار داده‌ایم تا این امکان را برای کاربران فراهم کنیم که در صورت لزوم از سیستم خارج شوند. این عمل خروج توسط متد
logout()
logout() از هوک
useAuth
useAuth انجام می‌شود.

در نهایت، در فایل

App.jsx
App.jsx، تمام مسیرهای موجود در
AuthProvider
AuthProvider را از هوک
useAuth
useAuth که قبلا ایجاد کرده بودیم کپسوله می‌کنیم تا یک context احراز هویت ثابت در سراسر برنامه‌ای که داریم فراهم شود. مسیرهای خود را طبق روش همیشگی تنظیم می‌کنیم و برای مسیرهایی که نیاز به احراز هویت دارند، از کامپوننت
<ProtectedRoute />
<ProtectedRoute /> استفاده می‌کنیم تا دسترسی را فقط به کاربران تأیید شده محدود نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// src/App.jsx
import { Routes, Route } from "react-router-dom";
import { LoginPage } from "./pages/Login";
import { HomePage } from "./pages/Home";
import { Secret } from "./pages/Secret";
import "./App.css";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { AuthProvider } from "./hooks/useAuth";
function App() {
return (
<AuthProvider>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
<Route
path="/secret"
element={
<ProtectedRoute>
<Secret />
</ProtectedRoute>
}
/>
</Routes>
</AuthProvider>
);
}
export default App;
// src/App.jsx import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import { Secret } from "./pages/Secret"; import "./App.css"; import { ProtectedRoute } from "./components/ProtectedRoute"; import { AuthProvider } from "./hooks/useAuth"; function App() { return ( <AuthProvider> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> <Route path="/secret" element={ <ProtectedRoute> <Secret /> </ProtectedRoute> } /> </Routes> </AuthProvider> ); } export default App;
// src/App.jsx

import { Routes, Route } from "react-router-dom";
import { LoginPage } from "./pages/Login";
import { HomePage } from "./pages/Home";
import { Secret } from "./pages/Secret";

import "./App.css";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { AuthProvider } from "./hooks/useAuth";

function App() {
  return (
    <AuthProvider>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/login" element={<LoginPage />} />
        <Route
          path="/secret"
          element={
            <ProtectedRoute>
              <Secret />
            </ProtectedRoute>
          }
        />
      </Routes>
    </AuthProvider>
  );
}

export default App;

با انجام این مراحل، یک جریان احراز هویت اولیه ایجاد کرده‌ایم که فقط به کاربران احراز هویت شده اجازه دسترسی به مسیرهای محافظت شده را می‌دهد. اگر سعی کنیم بدون ورود به سیستم به مسیر

/secret
/secret دسترسی پیدا کنید، فوراً به صفحه ورود هدایت می‌شویم. با این حال، پس از وارد کردن نام کاربری و رمز عبور پیش‌فرض در فرم ورود، می‌توانیم به صفحه secret دسترسی پیدا کنیم.

اگر تعداد محدودی از مسیرهای محافظت شده داشته باشیم، رویکرد فوق به خوبی کار می‌کند. با این حال، اگر چندین مسیر با این شرایط داشته باشیم، باید هر کدام را wrap کنیم که این موضوع می‌تواند کمی خسته‌کننده باشد.

برای رفع این مشکل، می‌توانیم از ویژگی مسیر تودرتو نسخه ۶ React Router استفاده کنیم تا تمام مسیرهای محافظت‌شده را در یک layout واحد قرار دهیم.

پیاده‌سازی احراز هویت دو مرحله‌ای با استفاده از نسخه ۶ React Router

در این بخش قصد داریم تا برنامه خود را با افزودن احراز هویت دو مرحله‌ای (۲FA) با React Router ارتقا دهیم. ۲FA با الزام کاربران به ارائه دو شکل مجزا از شناسایی قبل از دسترسی به ویژگی‌های حساس، یک لایه امنیتی اضافی اضافه می‌کند.

برای ادامه، ابتدا لازم است تا تنظیمات احراز هویت موجود را تغییر دهیم تا شامل ۲FA شود. فایل

useAuth.jsx
useAuth.jsx خود را با کد زیر به‌روزرسانی می‌نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// src/hooks/useAuth.jsx
import { createContext, useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "./useLocalStorage";
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useLocalStorage("user", null);
const [is2FAVerified, setIs2FAVerified] = useState(false);
const navigate = useNavigate();
const login = async (data) => {
setUser(data);
// Navigate to 2FA verification page
navigate("/verify-2fa");
};
const logout = () => {
setUser(null);
setIs2FAVerified(false);
navigate("/", { replace: true });
};
const verify2FACode = async (code) => {
// Mock verification logic
if (code === "0000") {
setIs2FAVerified(true);
navigate("/secret"); // Navigate to a protected route after successful 2FA
return true;
}
return false;
};
const value = {
user,
is2FAVerified,
login,
logout,
verify2FACode,
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export const useAuth = () => {
return useContext(AuthContext);
};
// src/hooks/useAuth.jsx import { createContext, useContext, useState } from "react"; import { useNavigate } from "react-router-dom"; import { useLocalStorage } from "./useLocalStorage"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useLocalStorage("user", null); const [is2FAVerified, setIs2FAVerified] = useState(false); const navigate = useNavigate(); const login = async (data) => { setUser(data); // Navigate to 2FA verification page navigate("/verify-2fa"); }; const logout = () => { setUser(null); setIs2FAVerified(false); navigate("/", { replace: true }); }; const verify2FACode = async (code) => { // Mock verification logic if (code === "0000") { setIs2FAVerified(true); navigate("/secret"); // Navigate to a protected route after successful 2FA return true; } return false; }; const value = { user, is2FAVerified, login, logout, verify2FACode, }; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }; export const useAuth = () => { return useContext(AuthContext); };
// src/hooks/useAuth.jsx

import { createContext, useContext, useState } from "react";
import { useNavigate } from "react-router-dom";
import { useLocalStorage } from "./useLocalStorage";

const AuthContext = createContext();

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

  const login = async (data) => {
    setUser(data);

    // Navigate to 2FA verification page
    navigate("/verify-2fa");
  };

  const logout = () => {
    setUser(null);
    setIs2FAVerified(false);
    navigate("/", { replace: true });
  };

  const verify2FACode = async (code) => {
    // Mock verification logic
    if (code === "0000") {
      setIs2FAVerified(true);
      navigate("/secret"); // Navigate to a protected route after successful 2FA
      return true;
    }
    return false;
  };

  const value = {
    user,
    is2FAVerified,
    login,
    logout,
    verify2FACode,
  };

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

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

در این فایل آپدیت شده، ما متد ورود به سیستم را برای ۲FA تغییر داده‌ایم و یک تابع

verify2FACode()
verify2FACode() اضافه کرده‌ایم که کد
۰۰۰۰
۰۰۰۰ را برای سادگی تایید می‌کند. اگر یک سناریوی واقعی را در نظر بگیریم، اینجا جایی است که ما باید تأیید واقعی ۲FA را اجرا نماییم، مانند ارسال یک کد از طریق پیامک یا ایمیل.

در مرحله بعد، یک کامپوننت صفحه جدید اضافه می‌کنیم که به کاربران این امکان را می‌دهد تا کد ۲FA ارسال شده به آن‌ها را در آن وارد کنند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
export const Verify2FA = () => {
const navigate = useNavigate();
const { verify2FACode } = useAuth();
const [code, setCode] = useState("");
const handleSubmit = async (e) => {
e.preventDefault();
const isValid = await verify2FACode(code);
if (isValid) {
navigate("/secret");
} else {
alert("Invalid code. Please try again.");
}
};
return (
<form onSubmit={handleSubmit}>
<input
type= "text"
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder= "Enter verification code"
/>
<button type="submit">Verify</button>
</form>
);
};
import { useState } from "react"; import { useNavigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const Verify2FA = () => { const navigate = useNavigate(); const { verify2FACode } = useAuth(); const [code, setCode] = useState(""); const handleSubmit = async (e) => { e.preventDefault(); const isValid = await verify2FACode(code); if (isValid) { navigate("/secret"); } else { alert("Invalid code. Please try again."); } }; return ( <form onSubmit={handleSubmit}> <input type= "text" value={code} onChange={(e) => setCode(e.target.value)} placeholder= "Enter verification code" /> <button type="submit">Verify</button> </form> ); };
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";

export const Verify2FA = () => {
  const navigate = useNavigate();
  const { verify2FACode } = useAuth();
  const [code, setCode] = useState("");

  const handleSubmit = async (e) => {
    e.preventDefault();
    const isValid = await verify2FACode(code);
    if (isValid) {
      navigate("/secret");
    } else {
      alert("Invalid code. Please try again.");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type= "text"
        value={code}
        onChange={(e) => setCode(e.target.value)}
        placeholder= "Enter verification code"
      />
      <button type="submit">Verify</button>
    </form>
  );
};

ما همچنین باید کامپوننت

ProtectedRoute
ProtectedRoute خود را به‌روزرسانی کنیم تا منطق تأیید ۲FA را یکپارچه نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Navigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
export const ProtectedRoute = ({ children }) => {
const { user, is2FAVerified } = useAuth();
if (!user) {
return <Navigate to="/login" />;
}
if (!is2FAVerified) {
return <Navigate to="/verify-2fa" />;
}
return children;
};
export default ProtectedRoute;
import { Navigate } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedRoute = ({ children }) => { const { user, is2FAVerified } = useAuth(); if (!user) { return <Navigate to="/login" />; } if (!is2FAVerified) { return <Navigate to="/verify-2fa" />; } return children; }; export default ProtectedRoute;
import { Navigate } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";

export const ProtectedRoute = ({ children }) => {
  const { user, is2FAVerified } = useAuth();

  if (!user) {
    return <Navigate to="/login" />;
  }
  if (!is2FAVerified) {
    return <Navigate to="/verify-2fa" />;
  }

  return children;
};

export default ProtectedRoute;

با راه‌اندازی کامپوننت

Verify2FA
Verify2FA جدید و کامپوننت
ProtectedRoute
ProtectedRoute، تنظیمات مسیر
App.jsx
App.jsx خود را تغییر می‌دهیم تا تعریف مسیر
verify-2fa
verify-2fa را شامل شود، مانند کد زیر:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Routes, Route } from "react-router-dom";
import { LoginPage } from "./pages/Login";
import { HomePage } from "./pages/Home";
import { Secret } from "./pages/Secret";
import { Verify2FA } from "./pages/Verify2FA";
import "./App.css";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { AuthProvider } from "./hooks/useAuth";
function App() {
return (
<AuthProvider>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/verify-2fa" element={<Verify2FA />} />
<Route path="/login" element={<LoginPage />} />
<Route
path="/secret"
element={
<ProtectedRoute>
<Secret />
</ProtectedRoute>
}
/>
</Routes>
</AuthProvider>
);
}
export default App;
import { Routes, Route } from "react-router-dom"; import { LoginPage } from "./pages/Login"; import { HomePage } from "./pages/Home"; import { Secret } from "./pages/Secret"; import { Verify2FA } from "./pages/Verify2FA"; import "./App.css"; import { ProtectedRoute } from "./components/ProtectedRoute"; import { AuthProvider } from "./hooks/useAuth"; function App() { return ( <AuthProvider> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/verify-2fa" element={<Verify2FA />} /> <Route path="/login" element={<LoginPage />} /> <Route path="/secret" element={ <ProtectedRoute> <Secret /> </ProtectedRoute> } /> </Routes> </AuthProvider> ); } export default App;
import { Routes, Route } from "react-router-dom";
import { LoginPage } from "./pages/Login";
import { HomePage } from "./pages/Home";
import { Secret } from "./pages/Secret";
import { Verify2FA } from "./pages/Verify2FA";

import "./App.css";
import { ProtectedRoute } from "./components/ProtectedRoute";
import { AuthProvider } from "./hooks/useAuth";

function App() {
  return (
    <AuthProvider>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/verify-2fa" element={<Verify2FA />} />
        <Route path="/login" element={<LoginPage />} />
        <Route
          path="/secret"
          element={
            <ProtectedRoute>
              <Secret />
            </ProtectedRoute>
          }
        />
      </Routes>
    </AuthProvider>
  );
}

export default App;

پس از راه‌اندازی مجدد برنامه، کاربران احراز هویت نشده هنگامی که سعی می‌کنند به مسیر محافظت شده

/secret
/secret دسترسی پیدا کنند به صفحه ورود هدایت می‌شوند. همچنین، اگر وارد سیستم شده‌اند اما هنوز ۲FA را نگذرانده‌اند، در این صورت، به صفحه تأیید ۲FA هدایت می‌گردند. کاربران تنها زمانی می‌توانند به مسیرهای محافظت شده دسترسی داشته باشند که هر دو مرحله احراز هویت را تکمیل کرده باشند.

ادغام Auth0 با React Router

یکی دیگر از الگوهای رایج احراز هویت، ادغام React Router با کتابخانه‌های احراز هویت third-party مانند Auth0 است. این فرآیند شامل ایجاد یک حساب کاربری Auth0، بازیابی اطلاعات کاربری و استفاده از کتابخانه‌هایی مانند

auth0-react
auth0-react برای اجرای یکپارچه فرآیند احراز هویت می‌باشد.

از آن جایی که ادغام Auth0 با React Router خارج از محدوده این مقاله است، یادگیری انجام این کار با مطالعه مستندات رسمی Auth0 می‌تواند بسیار مفید باشد.

استفاده از مسیرهای تودرتو و
<Outlet />
<Outlet />

یکی از قدرتمندترین ویژگی‌های نسخه ۶ React Router، مسیرهای تودرتو است. این ویژگی به ما این امکان را می‌دهد تا مسیری داشته باشیم که شامل مسیرهای child دیگری باشد. اکثر layoutهای ما با بخش‌هایی در URL همراه هستند، و React Router به طور کامل از آن پشتیبانی می‌کند.

برای مثال، می‌توانیم یک کامپوننت parent

<Route />
<Route /> را به مسیرهای
<HomePage />
<HomePage /> و
<LoginPage />
<LoginPage /> اضافه نماییم، مانند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { ProtectedLayout } from "./components/ProtectedLayout";
import { HomeLayout } from "./components/HomeLayout";
// ...
export default function App() {
return (
<Routes>
<Route element={<HomeLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
</Route>
<Route path="/dashboard" element={<ProtectedLayout />}>
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</Routes>
);
}
import { ProtectedLayout } from "./components/ProtectedLayout"; import { HomeLayout } from "./components/HomeLayout"; // ... export default function App() { return ( <Routes> <Route element={<HomeLayout />}> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Route> <Route path="/dashboard" element={<ProtectedLayout />}> <Route path="profile" element={<ProfilePage />} /> <Route path="settings" element={<SettingsPage />} /> </Route> </Routes> ); }
import { ProtectedLayout } from "./components/ProtectedLayout";
import { HomeLayout } from "./components/HomeLayout";
// ...

export default function App() {
  return (
    <Routes>
      <Route element={<HomeLayout />}>
        <Route path="/" element={<HomePage />} />
        <Route path="/login" element={<LoginPage />} />
      </Route>

      <Route path="/dashboard" element={<ProtectedLayout />}>
        <Route path="profile" element={<ProfilePage />} />
        <Route path="settings" element={<SettingsPage />} />
      </Route>
    </Routes>
  );
}

کامپوننت parent

<Route />
<Route /> نیز می‌تواند یک path داشته باشد و مسئول رندر کردن کامپوننت child
<Route />
<Route /> در صفحه است.

هنگامی که کاربر به

/dashboard/profile
/dashboard/profile می‌رود، روتر
<ProfilePage />
<ProfilePage /> را نمایش می‌دهد. برای اینکه این اتفاق بیفتد، المنت route مربوط به parent باید یک کامپوننت
<Outlet />
<Outlet /> برای رندر کردن المنت‌های child داشته باشد. کامپوننت
Outlet
Outlet به المنت‌های تودرتو در رابط کاربری این امکان را می‌دهد تا هنگام نمایش مسیرهای child قابل مشاهده باشند.

المنت route مربوط به parent همچنین می‌تواند منطق business مشترک و رابط کاربری اضافی داشته باشد. به عنوان مثال، در کامپوننت

<ProtectedLayout />
<ProtectedLayout />، منطق مسیر خصوصی را به همراه یک navigation bar مشترک قرار داده‌ایم که هنگام نمایش مسیرهای child قابل مشاهده خواهد بود:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
export const ProtectedLayout = () => {
const { user } = useAuth();
if (!user) {
return <Navigate to="/" />;
}
return (
<div>
<nav>
<Link to="/settings">Settings</Link>
<Link to="/profile">Profile</Link>
</nav>
<Outlet />
</div>
)
};
import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const ProtectedLayout = () => { const { user } = useAuth(); if (!user) { return <Navigate to="/" />; } return ( <div> <nav> <Link to="/settings">Settings</Link> <Link to="/profile">Profile</Link> </nav> <Outlet /> </div> ) };
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";

export const ProtectedLayout = () => {
  const { user } = useAuth();

  if (!user) {
    return <Navigate to="/" />;
  }

  return (
    <div>
      <nav>
        <Link to="/settings">Settings</Link>
        <Link to="/profile">Profile</Link>
      </nav>
      <Outlet />
    </div>
  )
};

به جای کامپوننت

<Outlet />
<Outlet />، می‌توانیم از هوک
useOutlet
useOutlet نیز استفاده کنیم که همان هدف را دنبال می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Link, Navigate, useOutlet } from "react-router-dom";
// ...
export const ProtectedLayout = () => {
const { user } = useAuth();
const outlet = useOutlet();
if (!user) {
return <Navigate to="/" />;
}
return (
<div>
<nav>
<Link to="/settings">Settings</Link>
<Link to="/profile">Profile</Link>
</nav>
{outlet}
</div>
);
};
import { Link, Navigate, useOutlet } from "react-router-dom"; // ... export const ProtectedLayout = () => { const { user } = useAuth(); const outlet = useOutlet(); if (!user) { return <Navigate to="/" />; } return ( <div> <nav> <Link to="/settings">Settings</Link> <Link to="/profile">Profile</Link> </nav> {outlet} </div> ); };
import { Link, Navigate, useOutlet } from "react-router-dom";
// ...

export const ProtectedLayout = () => {
  const { user } = useAuth();
  const outlet = useOutlet();

  if (!user) {
    return <Navigate to="/" />;
  }

  return (
    <div>
      <nav>
        <Link to="/settings">Settings</Link>
        <Link to="/profile">Profile</Link>
      </nav>
      {outlet}
    </div>
  );
};

مشابه مسیرهای محافظت شده، ما نمی‌خواهیم کاربران احراز هویت شده به path

/login
/login دسترسی داشته باشند. بنابراین، برای مدیریت آن در کامپوننت
<HomeLayout />
<HomeLayout /> به صورت زیر عمل می‌کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";
export const HomeLayout = () => {
const { user } = useAuth();
if (user) {
return <Navigate to="/dashboard/profile" />;
}
return (
<div>
<nav>
<Link to="/">Home</Link>
<Link to="/login">Login</Link>
</nav>
<Outlet />
</div>
)
};
import { Navigate, Outlet } from "react-router-dom"; import { useAuth } from "../hooks/useAuth"; export const HomeLayout = () => { const { user } = useAuth(); if (user) { return <Navigate to="/dashboard/profile" />; } return ( <div> <nav> <Link to="/">Home</Link> <Link to="/login">Login</Link> </nav> <Outlet /> </div> ) };
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../hooks/useAuth";

export const HomeLayout = () => {
  const { user } = useAuth();

  if (user) {
    return <Navigate to="/dashboard/profile" />;
  }

  return (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/login">Login</Link>
      </nav>
      <Outlet />
    </div>
  )
};

دسترسی به کد و دمو برنامه‌ای که نوشتیم از طریق این لینک امکان‌پذیر می‌باشد.

استفاده از APIهای data library نسخه ۶٫۴ React Router

پکیج React Router در نسخه ۶٫۴، روترها و data APIهای جدیدی را معرفی کرد. همه برنامه‌های وب برای فعال کردن دسترسی به data API باید از تابع

createBrowserRouter()
createBrowserRouter() استفاده کنند. سریع‌ترین راه برای به‌روزرسانی یک برنامه موجود به API جدید React Router این است که کامپوننت‌های
Route
Route را درون تابع
createRoutesFromElements()
createRoutesFromElements() قرار دهیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export const router = createBrowserRouter(
createRoutesFromElements(
<>
<Route element={<HomeLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
</Route>
<Route path="/dashboard" element={<ProtectedLayout />}>
<Route path="profile" element={<ProfilePage />} />
<Route path="settings" element={<SettingsPage />} />
</Route>
</>
)
);
export const router = createBrowserRouter( createRoutesFromElements( <> <Route element={<HomeLayout />}> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> </Route> <Route path="/dashboard" element={<ProtectedLayout />}> <Route path="profile" element={<ProfilePage />} /> <Route path="settings" element={<SettingsPage />} /> </Route> </> ) );
export const router = createBrowserRouter(
  createRoutesFromElements(
    <>
      <Route element={<HomeLayout />}>
        <Route path="/" element={<HomePage />} />
        <Route path="/login" element={<LoginPage />} />
      </Route>

      <Route path="/dashboard" element={<ProtectedLayout />}>
        <Route path="profile" element={<ProfilePage />} />
        <Route path="settings" element={<SettingsPage />} />
      </Route>
    </>
  )
);

در فایل

index.js
index.js، به جای کامپوننت
<BrowserRouter />
<BrowserRouter />، از کامپوننت
<RouterProvider />
<RouterProvider /> استفاده می‌کنیم و آبجکت
router
router export شده را از فایل
App.js
App.js ارسال می‌نماییم. همچنین باید به این نکته توجه داشته باشیم که
AuthProvider
AuthProvider بدون
BrowserRouter
BrowserRouter کار نخواهد کرد زیرا از تابع
useNavigate()
useNavigate() استفاده می‌کند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { router } from "./App";
...
root.render(
<StrictMode>
<ThemeProvider theme={theme}>
<RouterProvider router={router} />
</ThemeProvider>
</StrictMode>
);
import { router } from "./App"; ... root.render( <StrictMode> <ThemeProvider theme={theme}> <RouterProvider router={router} /> </ThemeProvider> </StrictMode> );
import { router } from "./App";
...
root.render(
  <StrictMode>
    <ThemeProvider theme={theme}>
      <RouterProvider router={router} />
    </ThemeProvider>
  </StrictMode>
);

برای استفاده از

AuthProvider
AuthProvider در context روتر، باید یک کامپوننت
<AuthLayout />
<AuthLayout /> ایجاد کنیم که المنت
outlet
outlet را درون
AuthProvider
AuthProvider قرار دهد. این کار تمام مسیرهای child را قادر می‌سازد تا به
AuthContext
AuthContext دسترسی داشته باشند:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { useLoaderData, useOutlet } from "react-router-dom";
import { AuthProvider } from "../hooks/useAuth";
export const AuthLayout = () => {
const outlet = useOutlet();
return (
<AuthProvider>{outlet}</AuthProvider>
);
};
import { useLoaderData, useOutlet } from "react-router-dom"; import { AuthProvider } from "../hooks/useAuth"; export const AuthLayout = () => { const outlet = useOutlet(); return ( <AuthProvider>{outlet}</AuthProvider> ); };
import { useLoaderData, useOutlet } from "react-router-dom";
import { AuthProvider } from "../hooks/useAuth";

export const AuthLayout = () => {
  const outlet = useOutlet();

  return (
    <AuthProvider>{outlet}</AuthProvider>
  );
};

اکنون می‌توانیم از کامپوننت

AuthLayout
AuthLayout به عنوان یک مسیر در سطح root استفاده کنیم، به عنوان مثال:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
export const router = createBrowserRouter(
createRoutesFromElements(
<Route
element={<AuthLayout />}
>
<Route element={<HomeLayout />}>
...
</Route>
<Route path="/dashboard" element={<ProtectedLayout />}>
...
</Route>
</Route>
)
);
export const router = createBrowserRouter( createRoutesFromElements( <Route element={<AuthLayout />} > <Route element={<HomeLayout />}> ... </Route> <Route path="/dashboard" element={<ProtectedLayout />}> ... </Route> </Route> ) );
export const router = createBrowserRouter(
  createRoutesFromElements(
    <Route
      element={<AuthLayout />}
    >
      <Route element={<HomeLayout />}>
        ...
      </Route>

      <Route path="/dashboard" element={<ProtectedLayout />}>
        ...
      </Route>
    </Route>
  )
);

در این مرحله، برنامه برای دسترسی به data APIها آماده است.

با data APIهای React Router، می‌توانیم نحوه دریافت داده‌ها را انتزاعی کنیم. معمولاً، ما داده‌ها را در داخل کامپوننت خود با استفاده از هوک

useEffect لود می‌کنیم. در عوض، می‌توانیم از تابع
loader()
loader() Router برای دریافت داده‌ها قبل از رندر المنت route استفاده نماییم.

مثالی را در نظر می‌گیریم که در آن باید هنگام لود برنامه، اطلاعات کاربری که وارد شده است را دریافت کنیم. بسته به اینکه کاربر، احراز هویت شده است یا نه می‌توانیم آن را به صفحه اصلی یا داشبورد هدایت نماییم.

برای شبیه سازی دریافت داده‌ها، می‌توانیم از

Promise
Promise با متد
setTimeout()
setTimeout() استفاده کنیم و
user
user را از
localStorage
localStorage دریافت نماییم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const getUserData = () =>
new Promise((resolve) =>
setTimeout(() => {
const user = window.localStorage.getItem("user");
resolve(user);
}, ۳۰۰۰)
);
const getUserData = () => new Promise((resolve) => setTimeout(() => { const user = window.localStorage.getItem("user"); resolve(user); }, ۳۰۰۰) );
const getUserData = () =>
  new Promise((resolve) =>
    setTimeout(() => {
      const user = window.localStorage.getItem("user");
      resolve(user);
    }, ۳۰۰۰)
  );

با استفاده از prop

loader
loader در کامپوننت Route، می‌توانیم
Promise
Promise –
getUserData()
getUserData() – را به کامپوننت
AuthLayout
AuthLayout با کمک تابع utility
defer()
defer() ارسال کنیم. تابع
defer()
defer() به ما این امکان را می‌دهد که قبل از رندر شدن کامپوننت
Route
Route به جای مقادیر resolve شده، promiseها را ارسال کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import {
Route,
createBrowserRouter,
createRoutesFromElements,
defer
} from "react-router-dom";
import { AuthLayout } from "./components/AuthLayout";
...
// ideally this would be an API call to server to get logged in user data
const getUserData = () =>
new Promise((resolve) =>
setTimeout(() => {
const user = window.localStorage.getItem("user");
resolve(user);
}, ۳۰۰۰)
);
export const router = createBrowserRouter(
createRoutesFromElements(
<Route
element={<AuthLayout />}
loader={() => defer({ userPromise: getUserData() })}
>
<Route element={<HomeLayout />}>
...
</Route>
<Route path="/dashboard" element={<ProtectedLayout />}>
...
</Route>
</Route>
)
);
import { Route, createBrowserRouter, createRoutesFromElements, defer } from "react-router-dom"; import { AuthLayout } from "./components/AuthLayout"; ... // ideally this would be an API call to server to get logged in user data const getUserData = () => new Promise((resolve) => setTimeout(() => { const user = window.localStorage.getItem("user"); resolve(user); }, ۳۰۰۰) ); export const router = createBrowserRouter( createRoutesFromElements( <Route element={<AuthLayout />} loader={() => defer({ userPromise: getUserData() })} > <Route element={<HomeLayout />}> ... </Route> <Route path="/dashboard" element={<ProtectedLayout />}> ... </Route> </Route> ) );
import {
  Route,
  createBrowserRouter,
  createRoutesFromElements,
  defer
} from "react-router-dom";

import { AuthLayout } from "./components/AuthLayout";
...
// ideally this would be an API call to server to get logged in user data
const getUserData = () =>
  new Promise((resolve) =>
    setTimeout(() => {
      const user = window.localStorage.getItem("user");
      resolve(user);
    }, ۳۰۰۰)
  );

export const router = createBrowserRouter(
  createRoutesFromElements(
    <Route
      element={<AuthLayout />}
      loader={() => defer({ userPromise: getUserData() })}
    >
      <Route element={<HomeLayout />}>
        ...
      </Route>

      <Route path="/dashboard" element={<ProtectedLayout />}>
        ...
      </Route>
    </Route>
  )
);

در کامپوننت

AuthLayout
AuthLayout، می‌توانیم با استفاده از هوک
useLoaderData
useLoaderData به
userPromise
userPromise دسترسی داشته باشیم.

کامپوننت

Await
Await می‌تواند مقادیر معوق را با مکانیزم مدیریت خطای داخلی رندر کند. کامپوننت
Await
Await باید داخل React Suspense قرار بگیرد تا یک رابط کاربری fallback فعال شود. در این مورد، ما یک progress bar خطی را رندر می‌کنیم تا زمانی که
userPromise
userPromise که داریم resolve شود.

ما می‌توانیم یک کامپوننت را به prop

errorElement
errorElement ارسال کنیم تا در صورت rejecte شدن
Promise
Promise، بتوانیم state رابط کاربری خطا را رندر نماییم.

در نهایت، می‌توانیم داده‌های کاربر را به عنوان مقدار اولیه به

AuthProvider
AuthProvider ارسال کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import { Suspense } from "react";
import { useLoaderData, useOutlet, Await } from "react-router-dom";
import LinearProgress from "@mui/material/LinearProgress";
import Alert from "@mui/material/Alert";
import { AuthProvider } from "../hooks/useAuth";
export const AuthLayout = () => {
const outlet = useOutlet();
const { userPromise } = useLoaderData();
return (
<Suspense fallback={<LinearProgress />}>
<Await
resolve={userPromise}
errorElement={<Alert severity="error">Something went wrong!</Alert>}
children={(user) => (
<AuthProvider userData={user}>{outlet}</AuthProvider>
)}
/>
</Suspense>
);
};
import { Suspense } from "react"; import { useLoaderData, useOutlet, Await } from "react-router-dom"; import LinearProgress from "@mui/material/LinearProgress"; import Alert from "@mui/material/Alert"; import { AuthProvider } from "../hooks/useAuth"; export const AuthLayout = () => { const outlet = useOutlet(); const { userPromise } = useLoaderData(); return ( <Suspense fallback={<LinearProgress />}> <Await resolve={userPromise} errorElement={<Alert severity="error">Something went wrong!</Alert>} children={(user) => ( <AuthProvider userData={user}>{outlet}</AuthProvider> )} /> </Suspense> ); };
import { Suspense } from "react";
import { useLoaderData, useOutlet, Await } from "react-router-dom";
import LinearProgress from "@mui/material/LinearProgress";
import Alert from "@mui/material/Alert";
import { AuthProvider } from "../hooks/useAuth";

export const AuthLayout = () => {
  const outlet = useOutlet();

  const { userPromise } = useLoaderData();

  return (
    <Suspense fallback={<LinearProgress />}>
      <Await
        resolve={userPromise}
        errorElement={<Alert severity="error">Something went wrong!</Alert>}
        children={(user) => (
          <AuthProvider userData={user}>{outlet}</AuthProvider>
        )}
      />
    </Suspense>
  );
};

برای تأیید شرایط خطا، می‌توانیم

Promise
Promise را مطابق شکل زیر reject کنیم:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// for error
const getUserData = () =>
new Promise((resolve, reject) =>
setTimeout(() => {
reject("Error");
}, ۳۰۰۰)
);
// for error const getUserData = () => new Promise((resolve, reject) => setTimeout(() => { reject("Error"); }, ۳۰۰۰) );
// for error
const getUserData = () =>
  new Promise((resolve, reject) =>
    setTimeout(() => {
      reject("Error");
    }, ۳۰۰۰)
  );

دسترسی به کد کامل نمونه احراز هویت ۲FA در لینک GitHub و نمونه کد ادغام data library در این لینک امکان‌پذیر می‌باشد.

جمع‌بندی

در این مقاله سعی کردیم نحوه مدیریت احراز هویت کاربران با استفاده از نسخه ۶ React Router را بررسی کنیم. نسخه ۶ React Router نسبت به نسخه‌های قبلی پیشرفت زیادی داشته است و سریع، پایدار و قابل اعتماد می‌باشد. علاوه بر این که کار کردن با آن بسیار آسان است، دارای بسیاری از ویژگی‌های جدید مانند

<Outlet />
<Outlet /> و یک کامپوننت بهبود یافته
<Route />
<Route /> است که مسیریابی را در برنامه‌های React بسیار ساده‌تر کرده است.

با routerها و data APIهای جدید موجود در نسخه ۶٫۴، می‌توانیم به راحتی stateهای optimistic UI، pending و error را مدیریت کنیم. همچنین، می‌توانیم در حین نمایش یک رابط کاربری fallback تا زمانی که داده‌ها آماده شوند، داده‌های خارج از کامپوننت را abstract کرده و لود نماییم.

دیدگاه‌ها:

افزودن دیدگاه جدید