احراز هویت در ری اکت ( هرچیزی که از Authentication در React باید بدونیم! )
احراز هویت در ری اکت یکی از مهمترین مفاهیمی هست که هر توسعه دهنده React باید بهش مسلط باشه و به بهترین شکل ممکن اون رو پیاده سازی کنه. تو این مقاله میخوایم مفهوم Authorization و Authentication در ری اکت رو بررسی کنیم.
ما باید از کاربران خودمون نام کاربری و پسورد بگیریم و اگه اطلاعاتشون درست بود، به صفحاتی مثل داشبورد منتقلشون کنیم.
همچنین باید از ورود کاربران احراز نشده به برخی صفحات جلوگیری کنیم. ( یعنی گارد در ری اکت )
تو این مقاله میخوایم بهترین و حرفه ای ترین روش احراز هویت در React رو باهمدیگه بررسی کنیم و یاد بگیریم 🙂
شروع احراز هویت در ریکت با ایجاد پروژه
برای اینکه بتونیم مبحث Authentication در React رو یادبگیریم، لازمه یک پروژه ری اکتی ایجاد کنیم تا این مبحث رو بصورت عملی باهمدیگه بررسی کنیم. با دستور زیر میتونیم یک پروژه ری اکتی ایجاد کنیم :
npm create vite@latest your-project-name -- --template react
و سپس به کمک دستور زیر میتونیم پروژه خودمون رو اجرا کنیم :
npm run dev
ساخت کامپوننت Login ( جهت ورود کاربران )
ما میخوایم یک کامپوننت به اسم Login درست کنیم که پروسه ورود کاربران رو انجام میده. این کامپوننت شامل دو Input ( یک فیلد ایمیل و یک فیلد پسورد ) میشه.
زمانیکه کاربر email و password خودش رو وارد میکنه، اطلاعات وارد شده داخل State تعریف شده در خط 5 ذخیره میشه.
سپس زمانیکه کاربر روی دکمه “ورود” کلیک میکنه، تابع handleSubmitEvent اجرا میشه.
import { Button, Input } from "antd";
import { useState } from "react";
const Login = () => {
const [input, setInput] = useState({
username: "",
password: "",
});
const handleSubmitEvent = (e) => {
e.preventDefault();
if (input.username !== "" && input.password !== "") {
//dispatch action from hooks
}
alert("please provide a valid input");
};
const handleInput = (e) => {
const { name, value } = e.target;
setInput((prev) => ({
...prev,
[name]: value,
}));
};
return (
);
};
export default Login;
تو کامپوننت بالا من از کتابخانه Antd استفاده کردم اما شما میتونید ازش استفاده نکنید ضمنا اگه علاقه داشتید میتونید از آموزش کتابخانه Ant Design برای یادگیری این کتابخونه مفید و قدرتمند استفاده کنید. خروجی کامپوننت بالا بصورت زیر هست :
ساخت AuthContext و AuthProvider برای احراز هویت در React !
ما از Context API برای این استفاده میکنیم که به یکسری State گلوبال که تو کل اپیکیشن بهشون نیاز داریم، دسترسی داشته باشیم. در حقیقت ما نیاز داریم که اطلاعات کاربر احراز شده رو در یک State گلوبال ذخیره کنیم و از کل اپیکیشن بهش دسترسی داشته باشیم.
اگه با خود Context آشنا نیستید، حتما پیشنهاد میکنم مقاله useContext در ری اکت رو مطالعه کنید.
ما میخوایم اطلاعات کاربر احراز شده ( Login شده ) رو ذخیره کنیم و در کامپوننت ها و صفحات مختلف بهش دسترسی داشته باشیم. حالا شاید بپرسیم چرا دسترسی داشته باشیم؟
مثلا تو کامپوننت هدر میخوایم نام و میزان موجودی کیف پول کاربر رو نشون بدیم یا تو برخی صفحات چک کنیم که کاربر وارد شده کی هست و اگه وارد نشده بود، کاربر رو logout کنیم.
برای اینکار من یک فایل به اسم AuthProvider.js میسازم و داخلش یک Context به اسم AuthContext درست میکنم :
import { useContext, createContext } from "react";
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
return {children} ;
};
export default AuthProvider;
export const useAuth = () => {
return useContext(AuthContext);
};
تو خط 2 تیکه کد بالا من یک Context ساختم. تو خط 10 نیز یک Custom Hook ساختم که بتونم از این Context خیلی راحت تر استفاده کنم و پروسه احراز هویت کاربر برای ما آسون تر مدیریت بشه.
تو خط 4 تیکه کد بالا ما AuthProvider رو ساختیم تا کل اپیکیشن خودمون رو داخل این HOC بندازیم. اینجوری تمام صفحات و کامپوننت های ما از این Logic استفاده میکنن ( منظورم Logic احراز هویت هست )
البته تیکه کد بالا کامل نیست و در ادامه کاملترش میکنم.
حالا باید AuthProvider خودمون که ساختیم رو دور کل اپیکیشن خودمون بندازیم، یعنی وارد فایل App.js یا main.js خودمون بشیم و بصورت زیر عمل کنیم :
import AuthProvider from "./hooks/AuthProvider";
function App() {
return (
{/* App content */}
);
}
export default App;
حالا Logic ما ( احراز هویت در ری اکت ) تو کل اپیکیشن ری اکتی ما قابل استفادس!
اما ما هیچ تابعی برای مدیریت پروسه Login و Logout ( ورود و خروج کاربر ) نداریم!
تکمیل پروسه Authentication در ری اکت
برای تکمیل پروسه احراز هویت در ری اکت لازمه که 2 تابع برای ورود و خروج کاربر ایجاد کنیم. برای اینکار وارد فایل AuthProvider میشیم و این 2 تابع رو ایجاد میکنیم :
import { useContext, createContext, useState } from "react";
import { useNavigate } from "react-router-dom";
const AuthContext = createContext();
const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [token, setToken] = useState(localStorage.getItem("site") || "");
const navigate = useNavigate();
const loginAction = async (data) => {
try {
const response = await fetch("your-api-endpoint/auth/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
const res = await response.json();
if (res.data) {
setUser(res.data.user);
setToken(res.token);
localStorage.setItem("site", res.token);
navigate("/dashboard");
return;
}
throw new Error(res.message);
} catch (err) {
console.error(err);
}
};
const logOut = () => {
setUser(null);
setToken("");
localStorage.removeItem("site");
navigate("/login");
};
return (
{children}
);
};
export default AuthProvider;
export const useAuth = () => {
return useContext(AuthContext);
};
تو خط 10 ما تابع loginAction رو ساختیم و میخوایم این تابع رو در صفحه Login زمانیکه کاربر نام کاربری و پسورد خودش رو وارد کرد صدا بزنیم. این تابع یک data به عنوان آرگومان ورودی میگیره.
این data همون نام کاربری و پسوردی هست که کاربر در صفحه ورود وارد میکنه.
تو خط 12 ما API خودمون که مرتبط با Login هست رو صدا میزنیم ( POST ) و نام کاربری و پسورد وارد شده توسط کاربر رو به API میفرستیم. سرور این اطلاعات رو بررسی میکنه ببینه درست هستن یا خیر.
اگر اطلاعات وارد شده توسط کاربر صحیح بود، در Response برای ما یک token و اطلاعات کاربر احراز شده رو میفرسته.
ما این اطلاعات رو در خط 21 و 22 داخل State خودمون ذخیره میکنیم. همچنین در خط 23 این اطلاعات رو داخل localstorage ذخیره میکنیم تا با رفرش شدن صفحه پاک نشن.
تو خط 24 هم کاربر احراز هویت شده رو به آدرس dashboard/ ریدایرکت میکنیم.
همچنین تو خط 33 تابع logOut رو ساختیم که پروسه خروج کاربر رو مدیریت میکنه. این تابع اطلاعات کاربر داخل State و localstorage رو پاک میکنه و کاربر رو به صفحه login/ ریدایرکت میکنه.
و این عالیه 🙂
الان دیگه هرموقع نیاز داشتیم میتونیم از تابع loginAction برای ورود کاربر و از تابع logOut برای خروج کاربر تو اپیکیشن خودمون استفاده کنیم، اما این تمام ماجرای احراز هویت در React نیست!
محافظت از آدرس ها با Authorization در ری اکت!
ما تو پروژه ری اکتی خودمون Route های خیلی زیادی داریم که باید ازشون محافظت کنیم ( Protecting Routes )
منظورم از محافظت این هست به کاربرانی که احراز هویت نشدن، اجازه نمایش اون صفحات رو ندیم!
در حقیقت ما باید یک Guard ( گارد ) بسازیم. برای اینکار من یک کامپوننت به اسم PrivateRoute میسازم :
import React from "react";
import { Navigate, Outlet } from "react-router-dom";
import { useAuth } from "../hooks/AuthProvider";
const PrivateRoute = () => {
const user = useAuth();
if (!user.token) return ;
return ;
};
export default PrivateRoute;
تو خط 3 تیکه کد بالا ما Custom Hook ( کاستوم هوک ) خودمون رو که قبلا ساخته بودیم فراخوانی کردیم و در خط 6،7 ازش استفاده کردیم تا ببینیم کاربر فعلی، token داره یا خیر!
اگر token وجود داشته باشه یعنی کاربر احراز هویت رو انجام داده، پس به کمک Outlet صفحه داشبورد رو بهش نمایش میدیم، در غیر اینصورت ( اگه کاربر احراز هویت نشده باشه ) به آدرس login/ ریدایرکتش میکنیم. ( اگه این پاراگراف رو متوجه نشدید صبور باشید تا تیکه کد بخش زیر رو یاد بگیریم )
پیاده سازی Routing ( به کمک React-Router )
ما به کمک react-router میتونیم صفحات خودمون رو مشخص کنیم تا کاربر امکان جابجایی بین صفحات مختلف رو داشته باشه.
برای اینکار باید وارد entry point پروژه ( فایل App.js ) بشیم و بصورت زیر عمل کنیم :
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Login from "./components/Login";
import Dashboard from "./components/Dashboard";
import AuthProvider from "./hooks/AuthProvider";
import PrivateRoute from "./router/route";
function App() {
return (
} />
}>
} />
{/* Other routes */}
);
}
export default App;
تو تیکه کد بالا من دو Route تعریف کردم که شامل صفحات login/ و dashboard/ میشه.
به خط 14 دقت کنید ..
تو خط 14 از PrivateRoute خودمون استفاده کردیم و طبق کدی که داخل این کامپوننت زدیم، اگه کاربر احراز هویت کرده باشه خط 15 ( dashboard/ ) براش نمایش داده میشه و میتونه داشبورد رو ببینه. ( بخاطر Outlet )
در حقیقت PrivateRoute به عنوان یک Guard ( گارد ) برای dashboard عمل میکنه.
تکمیل احراز هویت در React !
اگه تا اینجا همه موارد رو انجام دادید تبریک میگم بهتون 🙂
چون اکثر موارد رو تا اینجا پوشش دادیم.
ما یک کامپوننت Login ساختیم سپس یک AuthContext برای ذخیره اطلاعات کاربر احراز شده ساختیم و برای dashboard خودمون یک Guard ایجاد کردیم ( ازش محافظت کردیم )
حالا باید به کمک custom hook خودمون ( useAuth ) نام کاربری و پسورد کاربر در صفحه Login رو به تابع احراز هویت بفرستیم.
برای اینکار لازمه کامپوننت Login رو بصورت زیر تغییر بدیم :
import { useState } from "react";
import { useAuth } from "../hooks/AuthProvider";
const Login = () => {
const [input, setInput] = useState({
username: "",
password: "",
});
const auth = useAuth();
const handleSubmitEvent = (e) => {
e.preventDefault();
if (input.username !== "" && input.password !== "") {
auth.loginAction(input);
return;
}
alert("pleae provide a valid input");
};
return (
);
};
export default Login;
تو خط 14 تیکه کد بالا، اطلاعات وارد شده در فرم ( Input نام کاربری و پسورد ) رو به Context خودمون ارسال کردیم تا کاربر احراز هویت بشه.
فقط توجه داشته باشین که تو خط 22، من اِلِمان های داخل form رو قرار ندادم چون قبلا اینکار رو انجام دادیم باهم.
خروج از پنل به کمک دکمه خروج ( logOut )
اگه یادمون باشه ما یک تابع به اسم logOut ساختیم که اطلاعات ذخیره شده کاربر در State و localstorage رو پاک میکرد. اما از این تابع هیچوقت استفاده نکردیم!
برای اینکار ما باید یک دکمه به اسم “خروج” بسازیم و زمانیکه کاربر روش کلیک کرد تابع logOut رو صدا بزنیم :
import React, { useEffect } from "react";
import { useAuth } from "../hooks/AuthProvider";
const Dashboard = () => {
const auth = useAuth();
return (
Welcome! {auth.user?.username}
);
};
export default Dashboard;
الان اگه کاربر احراز هویت شده باشه، میتونه با کلیک روی دکمه “خروج” از پنل داشبورد خارج بشه. به دلیل پیاده سازی Guard ، پس از کلیک روی دکمه “خروج” کاربر بلافاصله از داشبورد خارج میشه و به صفحه Login ریدایرکت میشه.
نکته آخر !
ما تا به اینجای کار پروسه Authentication رو انجام دادیم اما اپیکیشن ما یه باگ بزرگ داره!
اطلاعات کاربر ( توکن، ایمیل و .. ) داخل State ذخیره میشه اما با رفرش شدن صفحه تمام اطلاعات ما پاک میشه و State خالی میشه!
در نتیجه بخاطر وجود Guard، کاربر از داشبورد خارج میشه و این اصلا خوب نیست!
ما باید اطلاعات احراز هویت کاربر رو داخل LocalStorage ذخیره کنیم و با هر بار Refresh شدن صفحه، اون اطلاعات رو از LocalStorage بخونیم و دوباره داخل State برزیم.
پس ما باید 2 کار انجام بدیم :
- ذخیره اطلاعات کاربر احراز شده در LocalStorage ( زمانی که کاربر احراز شد )
- خواندن اطلاعات کاربر از LocalStorage در فاز Mounting کامپوننت ( به کمک useEffect )
چند نکته خیلی مهم درمورد احراز هویت در ری اکت !
حالا که تونستیم به اصل موضوع Authentication در ری اکت مسلط بشیم، بیاید چندتا نکته مهم در این مورد باهمدیگه یاد بگیریم ..
سرویس های آماده پیاده سازی احراز هویت
ما تو این مقاله احراز هویت در ریکت رو بصورت دستی پیاده سازی کردیم اما میتونیم از سرویس های آماده برای Authentication استفاده کنیم که تمامی موارد رو بصورت اصولی و امن پیاده سازی کردن.
سرویس هایی مثل Auth0 ، Firebase Authentication یا Amazon Cognito و ..
پروسه احراز هویت رو به کمک HOC پیاده سازی کنیم !
زمانیکه از React استفاده میکنیم، بهتره منطق و مدیریت احراز هویت رو داخل یک HOC پیاده سازی کنیم و از مدیریت احراز هویت داخل هر کامپوننت اجتاب کنیم!
اینجوری از نوشتن کد تکراری جلوگیری میکنیم و با یکبار نوشتن منطق احراز هویت در ریکت، در تمامی اپیکیشن ازش استفاده میکنیم. ( تو این مقاله همین کار رو کردیم )
پروسه احراز هویت رو به کمک Context API پیاده سازی کنیم!
زمانیکه قصد پیاده سازی احراز هویت در React رو داریم، بهتره که از Context API به عنوان یک Global State Management استفاده کنیم. با اینکار میتونیم اطلاعات کاربر احراز هویت شده رو داخل این State ذخیره کنیم و از کل اپیکیشن به این اطلاعات دسترسی داشته باشیم. ( تو این مقاله همین کار رو کردیم )
محافظت از صفحات خصوصی با گارد
تکنیک Guard ( گارد ) کمک میکنه که فقط به کاربران احراز هویت شده اجازه دسترسی به صفحات خصوصی رو بدیم. تو این مقاله از همین تکنیک ( گارد در ری اکت ) استفاده کردیم.
پیاده سازی رفرش توکن در ری اکت ( Refresh Token در ری اکت )
هر توکن یک تاریخ انقضا داره و منقضی میشه، ما میتونیم به کمک تکنیک Refresh Token قبل از منتقضی شدن توکن، یک توکن جدید دریافت کنیم.
این کار بدون درخواست از کاربر برای “ورود مجدد” انجام میشه و خود کاربر متوجه موضوع نمیشه.
اگه Back-End یا سرویس احراز هویت شما از رفرش توکن پشتیبانی میکنه، حتما از این تکنیک استفاده کنید چون منقضی شدن توکن در مواقع حساس مثل سبد خرید، پرداخت هزینه و .. خیلی جالب نیست!
مدیریت خطاهای پروسه احراز هویت در React !
تو پروسه احراز هویت کاربر در ری اکت، قطعا کاربر با خطاهای مختلفی مواجه میشه. ما باید این ارور هارو مدیریت کنیم و به کاربر پاسخی مناسب نشون بدیم یا حتی به صفحه لاگین کاربر رو ریدایرکت کنیم. ( مثل توکن اشتباه، منقضی شدن توکن و .. )
پروسه احراز هویت رو نباید سخت کنیم!
ما باید پروسه احراز هویت کاربر رو آسون کنیم.
ترجیحا از گزینه “ورود باشبکه های اجتماعی یا گوگل” استفاده کنیم و گزینه “فراموشی پسورد” رو برای کاربر قرار بدیم.
ترجیحا توکن های خودمون رو داخل localStorage ذخیره نکنیم!
ذخیره کردن توکن در localStorage خطر آسیب پذیری XSS زیادی داره و اپیکیشن مارو در معرض آسیب و حملات امنیتی قرار میده. راه حل استفاده از HttpOnly cookies یا سرویس های احراز هویت هست که امنیت خیلی مناسبی دارن.
جمع بندی
ما یادگرفتیم چطور میتونیم به کمک Context و React Router پروسه احراز هویت کاربر در ری اکت رو پیاده سازی کنیم.
ما برای صفحات خودمون Guard ( گارد ) ساختیم تا فقط کاربرانی که احراز هویت شدن بتونن به این صفحات دسترسی داشته باشن.
همچنین از تمامی صفحات و کامپوننت های اپیکیشن، میتونیم به اطلاعات کاربر وارد شده دسترسی داشته باشیم.
پروسه احراز هویت در ریکت باید خیلی اصولی پیاده سازی بشه و نباید کثیف و پیچیده باشه، ما تو این مقاله به ساده ترین و بهترین شکل ممکن این پروسه رو پیاده سازی کردیم 🙂
درباره احمد احمدنژاد
من یه برنامه نویس و توسعه دهنده وب هستم که عاشق دنیای صفر و یکم❤️
نوشتههای بیشتر از احمد احمدنژاد6 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
تشکر از مقاله بسیار خوب شما
سلام امین جان خیلی خیلی خوشحالم که این مقاله برات مفید واقع شده و ممنونم از نظری که گذاشتی 🙂 موفق باشی
سپاس از مقاله خوبتون🙏
سلام و درود
ممنون از نظر لطفتون و خوشحالم که براتون مفید واقع شده 🙂
خسته نباشید خدمت شما . من هرکاری میکنم نمیتونم از context استفاده کنم . وقتی اسفاده میکنم route ها خونده نمیشن . ممنون میشم بگید چیکار کنم
سلام مهدی جان وقتت بخیر
سوالت حقیقتا خیلی کلی هست.
اگه به نتیجه ای نرسیدی تلگرام بهم پیام بده