برنامه نویسی 1131 بازدید

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

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

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

اهداف این سیستم احراز هویت

  • مسیرهای خصوصی و عمومی: این اپلیکیشن صفحه‌های landing پایه‌ای خواهد داشت که کاربر می‌تواند از آن‌ها بازدید کند. به همراه این مسیرها، مسیرهای ثبت نام و لاگین کردن باید به صورت مسیرهای عمومی اعلانی باشند. در سوی دیگر برخی صفحه‌ها وجود دارند که برای دیدن نیازمند احراز هویت هستند.
  • بازهدایت به لاگین: اگر کاربر دارای توکن نباشد، یا نیازمند رفرش توکن باشد، در صورت اقدام برای دیدن یک مسیر خصوصی، به صورت خودکار به صفحه لاگین بازهدایت می‌شود.
  • بازهدایت به ارجاع دهنده: اگر کاربر بخواهد یک صفحه خاص را ببیند، اما توکن معتبری نداشته باشد، به صفحه لاگین بازهدایت می‌شود. سپس باید مطمئن شویم که کاربر پس از لاگین کردن به صفحه‌ای که در ابتدا می‌خواست ببیند بازهدایت می‌شود. صفحه پیش‌فرض، داشبورد است.
  • توکن‌های احراز هویت: ما از توکن‌ها برای خواندن و نوشتن اطلاعات احراز هویت استفاده می‌کنیم. این توکن‌ها باید در storage لوکال ذخیره شوند تا کاربر بتواند در صورت ترک سایت، در حالت لاگین بماند.
  • UI شهودی و سرراست: این گزینه از نظر UI ضرورت فوری ندارد، اما ساده بودن صفحه‌های لاگین و ثبت نام مهم است.

گام‌های فوق باید به ترتیب برداشته شوند تا مطمئن شویم که این فرایند با کدهای بیش از حد پیچیده overload نشده است. بهتر است کد را ابتدا ساده نوشته و در ادامه بازسازی کنیم و به این ترتیب کل فرایند را درک کنیم تا این که از همان ابتدا انتظار داشته باشیم همه موارد در کد رعایت شده باشند. ما در این مقاله از Redux استفاده نخواهیم کرد و به جای آن داده‌هایی را که لازم داریم در react context ذخیره می‌کنیم تا فرایند کار ساده بماند. Redux در پس‌زمینه نیز کار مشابهی انجام می‌دهد، اما اپلیکیشن ما فعلاً چنان اندازه بزرگی ندارد که نیازی به استفاده از Redux داشته باشیم.

آغاز پروژه

در ابتدا برخی آماده‌سازی‌ها برای پروژه ابتدایی انجام می‌دهیم. این پروژه باید به صورت یک پروژه جدید آغاز شود، اما ما وانمود می‌کنیم که کار را از صفر آغاز کرده‌ایم تا امکان پیگیری مراحل از سوی شما آسان‌تر شود. کد کامل این پروژه را می‌توانید در این ریپوی گیت‌هاب (+) ملاحظه کنید. پروژه ابتدایی را با دستور Create React App به صورت زیر آغاز می‌کنیم:

npx create-react-app react-router-auth

ما در این پروژه از هیچ معماری permanent برای فایل‌های خود استفاده نمی‌کنیم. هر کس استایل خاص خود را برای کار با فایل‌ها و پوشه‌ها دارد و استفاده یا عدم استفاده از یک چنین معماری بر عهده کاربران است.

در ادامه پکیج‌هایی را که لازم است نصب می‌کنیم. ابتدا React Router و Styled Components و axios را نصب خواهیم کرد:

npm install --save styled-components react-router-dom axios

اکنون نوبت تنظیم react-router است. به این منظور کامپوننت App.js را تغییر می‌دهیم تا برخی مسیرهای ابتدایی داشته باشیم. فعلاً یک صفحه عمومی Home و یک صفحه عمومی Admin اضافه می‌کنیم. این موارد را در ادامه به صورت خصوصی درخواهیم آورد. توجه کنید که دو فایل را ایمپورت می‌کنیم که هنوز ایجاد نشده‌اند، چون فقط می‌خواهیم مسیر را پیش از وارد شدن به جزییات مورد نیاز هر صفحه ببینیم:

فایل src/App.js

import React from "react";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
import Home from './pages/Home';
import Admin from './pages/Admin';

function App(props) {
  return (
    <Router>
      <div>
        <ul>
          <li>
            <Link to="/">Home Page</Link>
          </li>
          <li>
            <Link to="/admin">Admin Page</Link>
          </li>
        </ul>
        <Route exact path="/" component={Home} />
        <Route path="/admin" component={Admin} />
      </div>
    </Router>
  );
}

export default App;

همچنین این دو کامپوننت را برای صفحه home و admin می‌سازیم. به این منظور پوشه جدیدی در دایرکتوری src به نام pages ایجاد می‌کنیم. قصد داریم دو صفحه جدید در این دایرکتوری بسازیم. نام آن‌ها را Home.js و Admin.js می‌گذاریم. البته نیازی به ایمپورت کردن آن‌ها وجود ندارد، زیرا قبلاً این کار را انجام داده‌ایم.

فایل src/pages/Home.js

import React from "react";

function Home(props) {
  return <div>Home Page</div>;
}

export default Home;

فایل src/pages/Admin.js

import React from "react";

function Admin(props) {
  return <div>Admin Page</div>;
}

export default Admin;

این مقدار برای راه‌اندازی ساختار اولیه پروژه کافی است. از این جا به بعد قطعه‌هایی از سیستم احراز هویت را به پروژه اضافه می‌کنیم. باید اطمینان پیدا کنید که با دستور npm start یک اجرای تست روی آن داشته‌اید. بدین ترتیب می‌توانید بین صفحه‌ها حرکت کنید هر چند هنوز تنها دو صفحه home و admin وجود دارند.

تاکنون چه کارهایی انجام دادیم؟

پیش از آن که وارد مراحل بعدی شویم، باید مطمئن شویم که درکی بنیادی از SPA-ها و react-router داریم. اگر قبلاً با روترها و SPA-ها کار کرده باشید، می‌توانید از مطالعه این بخش صرف‌نظر کنید.

Create-React-App یک پروژه خارق‌العاده است که کارهای زیادی را که برای ایجاد یک اپلیکیشن جدید React مورد نیاز است انجام می‌دهد. این دستور در پس‌زمینه قطعه‌های زیادی را که لازم هستند به خصوص برای ساخت ماژول کنار هم قرار می‌دهد. این موضوع یک مقاله مفصل است، اما در این مقاله باید اشاره کنیم که این دستور به ما امکان می‌دهد که روی خود اپلیکیشن متمرکز شویم و دیگر هیچ نگرانی در مورد پیکربندی نداشته باشیم.

اکنون روش‌های مختلف زیادی برای عرضه صفحه‌های وب به کاربران وجود دارند. به طور سنتی صفحه‌های وب از یک وب‌سرور به کاربران عرضه می‌شوند. کاربر باید به یک URL مانند http://www.dennyssweetwebsite.com/hello برود. سپس سرور میزبان آن وب‌سایت درخواست را دریافت کرده و صفحه‌ای که به دنبالش می‌گردد (در این مورد hello) را یافته و به کاربر به صورت hello.html بازگشت می‌دهد. این یک فایل HTML است که روی سرور قرار دارد.

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

از سوی دیگر دستور Create React App چارچوب کلی یک اپلیکیشن تک‌صفحه‌ای (SPA) را در سمت کلاینت می‌سازد. اپلیکیشن‌های تک‌صفحه‌ای وب‌اپلیکیشن‌هایی هستند که به طور کامل در مرورگر کاربر قرار می‌گیرند. زمانی که کاربر یک درخواست به www.dennyssweetwebsite.com ارسال می‌کند، به جای یک صفحه کل اپلیکیشن بازگشت می‌یابد. بدین ترتیب ما دیگر عملاً نیازی به URL-ها نداریم. آن جه کاربر می‌بیند می‌تواند مستقیماً به وسیله حالت مدیریت شود و دیگر نیازی به تغییر یافتن URL نیز وجود ندارد.

اما مشکل در این جا آن است که مرورگر و کاربران همچنان به URL-ها وابسته هستند. مرورگرها امکان حرکت به سمت جلو و عقب در تاریخچه صفحه‌ها را فراهم می‌سازند، امکان رفتن به صفحه‌های بوکمارک شده و غیره نیز وجود دارد. کاربران ممکن است صفحه‌های خاصی را بوکمارک کنند و بخواهند مستقیماً به آن صفحه‌ها بروند. همچنین حتی ممکن است URL برخی صفحات را به خاطر بسپارند و مستقیماً در مرورگر وارد نمایند. همچنین URL-ها روشی عالی برای جداسازی محتوا محسوب می‌شوند به خصوص در مواردی که موضوعاتی مبتنی بر مسیر مانند «بارگذاری با تأخیر» (lazy loading) مطرح باشد. به همین دلیل بسیاری از اپلیکیشن‌های تک‌صفحه‌ای همچنان از سیستم مسیریابی برای جداسازی محتواهایشان استفاده می‌کنند. تنها کاری که این سیستم انجام می‌دهد، خواندن URL مفروض و سپس نمایش یک کامپوننت برای آن URL به جای ارسال آن به سرور است. رندر کردن یک کامپوننت بر مبنای آن مسیر دقیقاً آن کاری است که در کامپوننت App.js فوق انجام دادیم.

اینک درس تاریخ به پایان می‌رسد و در ادامه شروع به ساخت برخی مسیرهای خصوصی می‌کنیم.

مسیرهای خصوصی و عمومی

نخستین چیزی که می‌خواهم بسازیم یک کامپوننت مسیر جدید است که آن را PrivateRoute می‌نامیم. این دکوراتور برای هر مسیری که لازم است در پشت احراز هویت قرار بگیرد، استفاده می‌شود. یک فایل جدید در دایرکتوری src به نام PrivateRoute.js ایجاد کنید.

فایل src/PrivateRoute.js

import React from 'react';
import { Route } from 'react-router-dom';

function PrivateRoute({ component: Component, ...rest }) {
  
  return(
    <Route {...rest} render={(props) => (
      <Component {...props} />
    )}
    />
  );
}

export default PrivateRoute;

شاید متوجه شوید که هنوز هیچ منطق احراز هویتی اضافه نکرده‌ایم. ما صرفاً مسیری که ارسال شده را مانند یک مسیر عمومی رندر می‌کنیم. البته API را کمی تغییر داده‌ایم. ما در این فایل از استایل Render Props برای مسیر استفاده می‌کنیم. این کار در ادامه که منطق احراز هویت را اضافه کردیم معنی بیشتری پیدا می‌کند. در حال حاضر فرض کنید این کد همان کاری را که با استفاده از props در Component برای مسیرهای عمومی انجام می‌دادیم اجرا می‌کند.

اکنون اگر به آن props رندر نگاه کنید، بدیهی است که باید نوعی کد احراز هویت درون آن داشته باشیم. اما هنوز چیزی را راه‌اندازی نکرده‌ایم. از آنجا که از Redux استفاده نخواهیم کرد، در سراسر اپلیکیشن خود باید از چیز دیگری برای احراز هویت بهره بگیریم. برای جلوگیری از نشت props قصد داریم از Context API استفاده کنیم. Context API را می‌توان این چنین تصور کرد که هر داده‌ای که در آن قرار می‌گیرد می‌تواند در درخت React به هر جایی انتقال یابد. این همان کاری است که ریداکس در پس‌زمینه انجام می‌دهد. قبل از هر چیز قصد داریم یک context جدید بسازیم. یک پوشه جدید به نام context در دایرکتوری src ایجاد می‌کنیم. درون این پوشه یک فایل به نام auth.js می‌سازیم.

فایل src/context/auth.js

import { createContext, useContext } from 'react';

export const AuthContext = createContext();

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

در این فایل context جدید خود را به همراه یک hook برای استفاده از context به نام useAuth می‌سازیم. در ادامه در این مورد بیشتر صحبت خواهیم کرد. در حال حاضر هیچ منطقی اجرا نمی‌شود و هر داده‌ای که در AuthContext پیدا کند جمع‌آوری می‌کند. برای استفاده از context جدید باید یک provider به react اضافه کنیم. این provider در فایل App.js اضافه می‌شود. در زمان انجام این کار همزمان مسیر Admin را نیز برای استفاده از کامپوننت جدید PrivateRoute عوض می‌کنیم.

فایل src/App.js

import React from "react";
import { BrowserRouter as Router, Link, Route } from "react-router-dom";
import PrivateRoute from './PrivateRoute';
import Home from "./pages/Home";
import Admin from "./pages/Admin";
import { AuthContext } from "./context/auth";

function App(props) {
  return (
    <AuthContext.Provider value={false}>
      <Router>
        <div>
          <ul>
            ...
          </ul>
          <Route exact path="/" component={Home} />
          <PrivateRoute path="/admin" component={Admin} />
        </div>
      </Router>
    </AuthContext.Provider>
  );
}

export default App;

بازهدایت به صفحه home

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

توجه داشته باشید که ما مقدار false را به Provider ارسال می‌کنیم. این بدان معنی است که قلاب useAuth ما همواره زمانی که احراز هویت را بررسی می‌کند، مقدار false بازگشت خواهد داد. از این رو همه مسیرهای خصوصی غیرقابل دسترسی می‌شوند. این وضعیت مناسبی نیست، اما برای تست کردن در حال حاضر خوب است. برای عملیاتی ساختن این کارکرد باید قلاب useAuth را به کامپوننت PrivateRoute اضافه کنیم:

فایل src/PrivateRoute.js

import React from "react";
import { Route, Redirect } from "react-router-dom";
import { useAuth } from "./context/auth";

function PrivateRoute({ component: Component, ...rest }) {
  const isAuthenticated = useAuth();

  return (
    <Route
      {...rest}
      render={props =>
        isAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect to="/" />
        )
      }
    />
  );
}

export default PrivateRoute;

در کد فوق از قلابمان استفاده می‌کنیم و هر مقداری که در AuthContext جمع‌آوری شده را دریافت می‌کنیم. در ادامه از توکن‌ها برای به‌روزرسانی این مقدار استفاده خواهیم کرد. در حال حاضر مقدار آن را false قرار می‌دهیم. این بدان معنی است که isAuthenticated همواره نادرست خواهد بود و از این رو زمانی که به منطق prop رندر مسیر ما برخورد کند، ما را به صفحه Home هدایت می‌کند. در ادامه این مورد را روی صفحه login تنظیم می‌کنیم اما در حال حاضر اگر آن را تست کنید، نمی‌توانید به صفحه Admin برسید و در صفحه Home گیر می‌کنید.

احراز هویت مقدماتی برای React

برای این که مطمئن شویم این قابلیت کار می‌کند، تلاش می‌کنیم مقدار Provider را در App.js به True عوض کنیم. در این صوت می‌توانید به هر جایی که دوست دارید بروید. در ادامه مقدار context را دوباره روی False قرار دهید تا به بخش بعدی برویم.

ایجاد صفحه Login و Signup

در این بخش صفحه‌های ورود و ثبت نام خود را ایجاد می‌کنیم. تلاش ما این است که این صفحه‌ها در حد امکان جمع‌وجور بمانند، اما با این حال اصول طراحی صفحه را نیز رعایت می‌کنیم. منظور از این حرف آن است که قصد داریم چند کامپوننت جدید که در صفحه‌ها استفاده خواهند‌ شد را ایجاد کنیم. ابتدا باید دو پوشه جدید در src به نام‌های components و img ایجاد کنیم.

کار خود را با یک کامپوننت AuthForm آغاز می‌کنیم. برای ساده‌تر ماندن کد، این کامپوننت فقط برخی کامپوننت‌های استایل‌دار را خواهد داشت که بین صفحه‌های ورود و ثبت نام مشترک هستند.

فایل src/components/AuthForm.js

import styled from 'styled-components';

const Card = styled.div`
  box-sizing: border-box;
  max-width: 410px;
  margin: 0 auto;
  padding: 0 2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
`;

const Form = styled.div`
  display: flex;
  flex-direction: column;
  width: 100%;
`;

const Input = styled.input`
  padding: 1rem;
  border: 1px solid #999;
  margin-bottom: 1rem;
  font-size: 0.8rem;
`;

const Button = styled.button`
  background: linear-gradient(to bottom, #6371c7, #5563c1);
  border-color: #3f4eae;
  border-radius: 3px;
  padding: 1rem;
  color: white;
  font-weight: 700;
  width: 100%;
  margin-bottom: 1rem;
  font-size: 0.8rem;
`;

const Logo = styled.img`
  width: 50%;
  margin-bottom: 1rem;
`;

const Error = styled.div`
  background-color: red;
`;

export { Form, Input, Button, Logo, Card, Erroer };

این کامپوننت‌ها کاملاً گویا هستند. از آنجا که نقطه تمرکز ما به طور عمده روی منطق مسیریابی خصوصی است، قصد نداریم زمان زیادی را صرف توضیح این موارد بکنیم، اما می‌توانیم از این کامپوننت‌ها برای ساختن صفحه‌های ورود و ثبت نام استفاده کنیم.

تنها قطعه لازم دیگر که باید از قبل راه‌اندازی کنیم، لوگویی است که در فرم‌های ثبت نام و ورود استفاده می‌شود. ما یک لوگوی دلخواه در src/img/logo.jpg قرار می‌دهیم که در صفحه نمایان خواهد شد. اینک صفحه‌های ورود و ثبت نام را می‌سازیم.

فایل src/pages/Login.js

import React from "react";
import { Link } from 'react-router-dom';
import logoImg from "../img/logo.jpg";
import { Card, Logo, Form, Input, Button } from '../components/AuthForms';

function Login() {
  return (
    <Card>
      <Logo src={logoImg} />
      <Form>
        <Input type="email" placeholder="email" />
        <Input type="password" placeholder="password" />
        <Button>Sign In</Button>
      </Form>
      <Link to="/signup">Don't have an account?</Link>
    </Card>
  );
}

export default Login;

فایل src/pages/Signup.js

import React from "react";
import { Link } from 'react-router-dom';
import logoImg from "../img/logo.jpg";
import { Card, Logo, Form, Input, Button } from '../components/AuthForms';

function Signup() {
  return (
    <Card>
      <Logo src={logoImg} />
      <Form>
        <Input type="email" placeholder="email" />
        <Input type="password" placeholder="password" />
        <Input type="password" placeholder="password again" />
        <Button>Sign Up</Button>
      </Form>
      <Link to="/login">Already have an account?</Link>
    </Card>
  );
}

export default Signup;

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

فایل src/App.js

import React from "react";
...
import Login from "./pages/Login";
import Signup from './pages/Signup';
...

function App(props) {
  return (
    <AuthContext.Provider value={false}>
      <Router>
        ...
          <Route exact path="/" component={Home} />
          <Route path="/login" component={Login} />
          <Route path="/signup" component={Signup} />
          <PrivateRoute path="/admin" component={Admin} />
       ...

اکنون شما باید بتوانید با وارد کردن URL به صفحه‌های ورود و ثبت نام بروید. ما دکمه‌ها را نیز اضافه خواهیم کرد، اما اینک باید بتوانید بین این دو صفحه با زدن لینک زیر دکمه Sign in/ Sign up به جلو و عقب حرکت کنید.

احراز هویت مقدماتی برای React

آخرین بخشی که اضافه می‌کنیم در مورد حالتی است که کاربر تلاش می‌کند به مسیر خصوصی برود و می‌خواهیم کاربر را به صفحه login بازگشت دهیم. این کار نیازمند یک تغییر کوچک در redirect کامپوننت PrivateRoute است.

فایل src/PrivateRoute.js

...
return (
    <Route
      {...rest}
      render={props =>
        isAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect to="/login" />
        )
      }
    />
  );
...

احراز هویت

قصد داریم در این بخش نوعی احراز هویت مقدماتی به وب اپلیکیشن خود اضافه کنیم. البته قرار نیست به صورت عمیقی وارد مباحث امنیتی شویم، چون این موضوع نیازمند یک مقاله مفصل جداگانه‌ای است. در حال حاضر قصد داریم یک سیستم توکن ایجاد کنیم که با یک نام کاربری و رمز عبور یک نقطه انتهایی login را فراخوانی کند و آن توکن‌ها را در state و همچنین در local storage ذخیره نماید. زمانی که از یک صفحه مسیر خصوصی بازدید می‌کنیم به بررسی state برای توکن می‌پردازیم. اگر هیچ توکنی وجود نداشته باشد، به بررسی local storage می‌پردازیم. اگر در آنجا هم چیزی بود، کاربر را به صفحه Login هدایت می‌کنیم. هان طور که می‌دانید بسیاری از قطعات این پازل را در بخش‌های قبلی آماده کرده‌ایم اینک باید منطق آن را تنظیم کنیم.

گام نخست: فایل App.js را با نوعی state جدید برای چارچوب auth provider خود به‌روزرسانی می‌کنیم. با استفاده از state در context provider به داده‌های context خود اجازه می‌دهیم که دینامیک باشند، یعنی لازم نیست پیش از زمان اجرا تنظیم شوند. این موارد می‌توانند بسته به ورودی کاربر تغییر یابند.

فایل App.js

import React, { useState } from "react"
...
function App(props) {
  const [authTokens, setAuthTokens] = useState();
  
  const setTokens = (data) => {
    localStorage.setItem("tokens", JSON.stringify(data));
    setAuthTokens(data);
  }

  return (
    <AuthContext.Provider value={{ authTokens, setAuthTokens: setTokens }}>
     ...
    </AuthContext.Provider>
  );
}
...

اکنون هر کامپوننتی که از AuthContext استفاده می‌کند، می‌تواند توکن‌ها را بگیرد و آن‌ها را تنظیم کند. منطق را در صفحه Login خود وارد می‌کنیم. در همین بخش با استفاده از useState hook در فرم Login، حالت (State) را اضافه می‌کنیم و به کاربر امکان می‌دهیم که روی Sign In کلیک کند تا گردش کار Login به راه بیفتد.

نکته: ما یک فراخوانی Axios اضافه کرده‌ایم. بدیهی است که URL ارسالی یک URL واقعی نیست و باید به جایی که یک توکن را توزیع می‌کند اشاره کند.

فایل Login.js

import React, { useState } from "react";
import { Link, Redirect } from "react-router-dom";
import axios from 'axios';
import logoImg from "../img/logo.jpg";
import { Card, Logo, Form, Input, Button, Error } from "../components/AuthForms";
import { useAuth } from "../context/auth";

function Login() {
  const [isLoggedIn, setLoggedIn] = useState(false);
  const [isError, setIsError] = useState(false);
  const [userName, setUserName] = useState("");
  const [password, setPassword] = useState("");
  const { setAuthTokens } = useAuth();

  function postLogin() {
    axios.post("https://www.somePlace.com/auth/login", {
      userName,
      password
    }).then(result => {
      if (result.status === 200) {
        setAuthTokens(result.data);
        setLoggedIn(true);
      } else {
        setIsError(true);
      }
    }).catch(e => {
      setIsError(true);
    });
  }

  if (isLoggedIn) {
    return <Redirect to="/" />;
  }

  return (
    <Card>
      <Logo src={logoImg} />
      <Form>
        <Input
          type="username"
          value={userName}
          onChange={e => {
            setUserName(e.target.value);
          }}
          placeholder="email"
        />
        <Input
          type="password"
          value={password}
          onChange={e => {
            setPassword(e.target.value);
          }}
          placeholder="password"
        />
        <Button onClick={postLogin}>Sign In</Button>
      </Form>
      <Link to="/signup">Don't have an account?</Link>
        { isError &&<Error>The username or password provided were incorrect!</Error> }
    </Card>
  );
}

export default Login;

در این بخش نمی‌خواهیم وارد جزییات گردش کار ثبت نام شویم، چون تا حدود زیادی با فرایند ورود یکسان است. تنها تفاوت‌ها در الزام فیلد رمز عبور برای بار دوم، احتمالاً برخی اطلاعات فردی و استفاده از URL ثبت نام به جای ورود است.

اینک از آنجا که این موضوع را به گردش توکن برده‌ایم و اشیای درون Auth Context را روی App.js تغییر داده‌ایم، متغیر isAuthenticated در PrivateRoute.js در عمل به شیئی اشاره می‌کند که مانند زیر است:

{
    authTokens: 'some token string',
    setAuthTokens: func
}

حتی اگر هیچ مقداری انتساب نیافته باشد، isAuthenticated باید true شود، چون شیء همواره وجود دارد. به منظور ساده ماندن این راهنمای آموزشی قصد داریم، فرض کنیم که داشتن authTokens به معنای احراز هویت شدن است. در ادامه زمانی که یک فراخوانی اتفاق بیفتد که مشخص شود توکن‌های احراز هویت ما منقضی شده‌اند، این توکن و همچنین local storage را پاک می‌کنیم. این اتفاق هر زمان که کاربری از نرم‌افزار خارج شود نیز اتفاق می‌افتد.

ابتدا از قلاب جدید useAuth برای دریافت توکن‌ها از context استفاده می‌کنیم. در ادامه بررسی می‌کنیم که آیا authTokens تعیین شده‌اند یا نه و اگر چنین باشد کامپوننت را رندر کرده و در غیر این صورت آن‌ها را به صفحه ورود هدایت می‌کنیم.

فایل PrivateRoute.js

import React from "react";
import { Route, Redirect } from "react-router-dom";
import { useAuth } from "./context/auth";

function PrivateRoute({ component: Component, ...rest }) {
  const { authTokens } = useAuth();

  return (
    <Route
      {...rest}
      render={props =>
        authTokens ? (
          <Component {...props} />
        ) : (
          <Redirect to="/login" />
        )
      }
    />
  );
}

export default PrivateRoute;

مدیریت فرایند خروج و توکن‌های منقضی شده

آخرین بخش از فرایند احراز هویت مدیریت توکن‌های منقضی شده یا حذف توکن‌ها در زمان خروج (Logout) کاربر از اپلیکیشن است. نخستین بخش گردش کار برای خروج کاربر ساده است و مبنایی برای ادامه فرایند در اختیار ما قرار می‌دهد.

زمانی که کاربر از اپلیکیشن خارج می‌شود، می‌توانیم تصور کنیم که همه توکن‌ها چه در state و چه در local storage باید حذف شوند. یک دکمه ساده برای این منظور در صفحه Admin ایجاد می‌کنیم. باید بدانید که در این صفحه قرار است دکمه‌های زیادی وجود داشته باشد.

فایل Admin.js

import React from "react";
import { Button } from "../components/AuthForms";
import { useAuth } from "../context/auth";

function Admin(props) {
  const { setAuthTokens } = useAuth();

  function logOut() {
    setAuthTokens();
  }

  return (
    <div>
      <div>Admin Page</div>
      <Button onClick={logOut}>Log out</Button>
    </div>
  );
}

export default Admin;

بازهدایت به ارجاع دهنده پس از ورود

در این بخش باید روشی که کاربران به وب اپلیکیشن ما می‌رسند را در نظر بگیریم. تا این نقطه می‌توانیم فرض کنیم که یک کاربر وارد صفحه‌ای اصلی ما شده و تصمیم می‌گیرد که وارد اپلیکیشن شود، بنابراین به مسیر Login یا SignIn می‌رود و سپس به داشبورد می‌رسد.

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

فایل Login.js

...
function Login(props) {
 ...
  const referer = props.location.state.referer || '/';

  ...

  if (isLoggedIn) {
    return <Redirect to={referer} />;
  }
  ...

اینک زمانی که کاربر وارد اپلیکیشن شد، یا به ارجاع دهنده هدایت می‌شود یا به صفحه Home می‌رود. این حالت جدید را در بازهدایت PrivateRoute.js خود اضافه می‌کنیم:

فایل PrivateRoute.js

...
function PrivateRoute({ component: Component, ...rest }) {
  ...

  return (
    ...
          <Redirect
            to={{ pathname: "/login", state: { referer: props.location } }}
          />
    ...

کاربر اینک باید پس از ورود به صفحه‌ای که در ابتدا قصد داشت ببیند بازهدایت شود.

سخن پایانی

بدین ترتیب به پایان این مقاله می‌رسیم. اما هنوز کارهای زیادی مانده است که باید انجام داد. برخی موارد مهم در ادامه ارائه شده‌اند:

  • به یک شیء User نیاز داریم که شامل داده‌های ضروری کاربر باشد.
  • باید توکن را در زمان آغاز به کار اولیه اپلیکیشن بررسی کنیم تا منقضی نشده باشد.
  • باید مجوزهای آبشاری طراحی کنیم که در آن یک مدیر وقتی از restriction استفاده می‌کند، خودش و همه مراتب بالاتر از آن دارای آن مجوز روی صفحه باشند.
  • بهبود امنیت، لاگ کردن خطا و موارد دیگر.

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

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.

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

بر اساس رای 11 نفر

آیا این مطلب برای شما مفید بود؟

نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *