使用 React.js 进行身份验证 我们需要做什么?该怎么做?

2025-05-25

使用 React.js 进行身份验证

我们需要做什么?

我们将如何做?

我们将使用hookscontext。我们只会使用基本概念,您无需在此用例中深入研究此主题。

我们需要做什么?

  1. 创建一个只有登录后才能访问的页面(我们需要创建两个页面:用户登录的登录页面和用户登录后进入的面板页面。用户只有在登录后才能访问面板页面。如果他试图直接访问面板,我们需要将他重定向到登录页面);
  2. 如果用户已经登录并刷新页面,他应该停留在面板页面,而不是被重定向到登录页面;

我们将如何做?

  1. 我们将创建一个名为PrivateRoute的组件,只有通过 SignIn 页面后才能访问;
  2. 我们将把用户令牌保存在localStorage中,以便当他退出或刷新页面时,他可以直接访问面板。

现在我们明白了我们要做什么,我们可以开始编码了。

创建我们的组件:Panel 和 SignIn

首先,在src文件夹中,创建一个名为screens的新文件夹。在这里,我们将创建Panel.jsSignIn.js。我将使用 bootstrap 来更快地设置组件的样式。如果你想这样做,但不知道如何安装 bootstrap,请查看此处

src/screens/Panel.js中:

import React from "react";
import { Button } from "react-bootstrap";

const Panel = () => {
  const onLogOut = () => {
    console.log('LogOut pressed.'); // we will change it later
  }
  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <div style={{ width: 300 }}>
        <h1 className="text-center"> Hello, user </h1>
        <Button
          variant="primary"
          type="button"
          className="w-100 mt-3 border-radius"
          onClick={onLogOut}
        >
          Log out
        </Button>
      </div>
    </div>
  );
};

export default Panel;
Enter fullscreen mode Exit fullscreen mode

src/screens/SignIn.js中:

import React, { useState} from 'react';
import { Form, Button } from 'react-bootstrap';

const SignIn = () => {
  const [email, setEmail] = useState();
  const [password, setPassword] = useState();

  const onFormSubmit = e => {
    e.preventDefault();
    console.log(email);
    console.log(password);
    // we will change it later;
  };
  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <div style={{ width: 300 }}>
        <h1 className="text-center">Sign in</h1>
        <Form onSubmit={onFormSubmit}>
          <Form.Group>
            <Form.Label>Email address</Form.Label>
            <Form.Control
              type="email"
              placeholder="Enter email"
              onChange={e => {
                setEmail(e.target.value);
              }}
            />
          </Form.Group>

          <Form.Group>
            <Form.Label>Password</Form.Label>
            <Form.Control
              type="password"
              placeholder="Password"
              onChange={e => {
                setPassword(e.target.value);
              }}
            />
          </Form.Group>
          <Button
            variant="primary"
            type="submit"
            className="w-100 mt-3"
          >
            Sign in
          </Button>
        </Form>
      </div>
    </div>
  );
};

export default SignIn;
Enter fullscreen mode Exit fullscreen mode

现在我们需要创建路由器。我们将在App.js中创建。为了在应用中导航,我们将使用react-router-dom。我们需要使用 yarn 或 npm 安装它:

yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode

现在在src/App.js中我们将为我们的应用程序创建路由。

import React from 'react';
import { Switch, BrowserRouter, Route } from 'react-router-dom';
import SignIn from './screens/SignIn';
import Panel from './screens/Panel';

function App() {
  return (
    <BrowserRouter>
        <Switch>
          <Route path="/sign-in" component={SignIn} />
          <Route path="/" component={Panel} />
        </Switch>
    </BrowserRouter>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

在上下文中保存用户令牌

现在我们需要创建一个上下文,以便能够在多个组件中访问用户令牌。即使在这个例子中我们只有两个组件,但在实际应用中,我们会有更多组件,其中很多组件都需要用户信息。

我们将在 src 文件夹中创建一个名为contexts的文件夹,并创建AuthContext.js

src/contexts/AuthContext.js中:

import React, { createContext, useState } from 'react';

export const authContext = createContext({});

const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState({ loading: true, data: null });
// we will use loading later


  const setAuthData = (data) => {
    setAuth({data: data});
  };
 // a function that will help us to add the user data in the auth;

  return (
    <authContext.Provider value={{ auth, setAuthData }}>
      {children}
    </authContext.Provider>
  );
};

export default AuthProvider;
Enter fullscreen mode Exit fullscreen mode

为了能够在整个应用程序中使用我们的上下文,我们需要将App组件包装在AuthProvider中。为此,我们进入src/index.js

...
import AuthProvider from './contexts/AuthContext';

ReactDOM.render(
  (
    <AuthProvider>
      <App />
    </AuthProvider>
  ),
  document.getElementById('root'),
);

...
Enter fullscreen mode Exit fullscreen mode

现在我们需要将用户凭证从SignIn组件传递给上下文。理想情况下,你只需要将 token 发送到上下文,但在本例中,我们将发送用户电子邮件,因为我们没有后端提供电子邮件。

src/screens/SignIn.js中:

...
import React, { useState, useContext } from 'react';
import { authContext } from '../contexts/AuthContext';

const SignIn = ({history}) => {
  ...
  const { setAuthData } = useContext(authContext);


  const onFormSubmit = e => {
    e.preventDefault();
    setAuthData(email); // typically here we send a request to our API and in response, we receive the user token.
 //As this article is about the front-end part of authentication, we will save in the context the user's email.
   history.replace('/'); //after saving email the user will be sent to Panel;
  };

  ...

};

export default SignIn;

Enter fullscreen mode Exit fullscreen mode

另外,当用户点击面板上的“注销”按钮时,我们需要清除上下文。我们将添加用户邮箱地址,而不是“Hello, user”。在src/screens/Panel.js中:

import React, {useContext} from "react";
import { Button } from "react-bootstrap";
import { authContext } from "../contexts/AuthContext";


const Panel = () => {
  const { setAuthData, auth } = useContext(authContext);
  const onLogOut = () => {
    setAuthData(null);
  } //clearing the context
  return (
    <div
      style={{ height: "100vh" }}
      className="d-flex justify-content-center align-items-center"
    >
      <div style={{ width: 300 }}>
        <h1 className="text-center"> {`Hello, ${auth.data}`} </h1>
        <Button
          variant="primary"
          type="button"
          className="w-100 mt-3"
          onClick={onLogOut}
        >
          Log out
        </Button>
      </div>
    </div>
  );
};

export default Panel;
Enter fullscreen mode Exit fullscreen mode

创建 PrivateRoute

现在我们需要让面板仅在登录后才能访问。为此,我们需要创建一个名为PrivateRoute的新组件。我们正在创建src/components/PrivateRote.js

import React, { useContext } from 'react';
import { Route, Redirect } from 'react-router-dom';
import { authContext } from '../contexts/AuthContext';

const PrivateRoute = ({ component: Component, ...rest }) => {
  const { auth } = useContext(authContext);
  return (
    <Route
      {...rest}
      render={(routeProps) => (
        auth.data ? <Component {...routeProps} /> : <Redirect to="/sign-in" />
      )}
    />

  );
/*  we are spreading routeProps to be able to access this routeProps in the component. */
};

export default PrivateRoute;
Enter fullscreen mode Exit fullscreen mode

如果用户未登录,我们将把他重定向到SignIn组件。现在我们需要在src/App.js
中使用 PrivateRoute

...
import PrivateRoute from './components/PrivateRoute';
function App() {
  return (
    <BrowserRouter>
        <Switch>
          <Route path="/sign-in" component={SignIn} />
          <PrivateRoute path="/" component={Panel} />
        </Switch>
    </BrowserRouter>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

管理 localStorage

现在一切正常,但如果刷新Panel页面,我们将返回到SignIn页面。我们希望浏览器记住用户。因此,我们将使用localStorage 。LocalStorage 是浏览器中存储数据的地方。localStorage 的问题在于它会降低应用程序的速度。我们需要明智地使用它,并加入 useEffect 函数来确保代码只执行一次。我们将在src/contexts/AuthContext.js中完成所有操作

import React, { createContext, useState, useEffect } from 'react';

export const authContext = createContext({});

const AuthProvider = ({ children }) => {
  const [auth, setAuth] = useState({ loading: true, data: null });

  const setAuthData = (data) => {
    setAuth({data: data});
  };

  useEffect(() => {
    setAuth({ loading: false, data: JSON.parse(window.localStorage.getItem('authData'))});
  }, []);
//2. if object with key 'authData' exists in localStorage, we are putting its value in auth.data and we set loading to false. 
//This function will be executed every time component is mounted (every time the user refresh the page);

  useEffect(() => {
    window.localStorage.setItem('authData', JSON.stringify(auth.data));
  }, [auth.data]);
// 1. when **auth.data** changes we are setting **auth.data** in localStorage with the key 'authData'.

  return (
    <authContext.Provider value={{ auth, setAuthData }}>
      {children}
    </authContext.Provider>
  );
};

export default AuthProvider;
Enter fullscreen mode Exit fullscreen mode

现在在src/components/PrivateRoute.js中:

const PrivateRoute = ({ component: Component, ...rest }) => {
  const { auth } = useContext(authContext);
  const { loading } = auth;

  if (loading) {
    return (
      <Route
        {...rest}
        render={() => {
          return <p>Loading...</p>;
        }}
      />
    );
  }
// if loading is set to true (when our function useEffect(() => {}, []) is not executed), we are rendering a loading component;

  return (
    <Route
      {...rest}
      render={routeProps => {
        return auth.data ? (
          <Component {...routeProps} />
        ) : (
          <Redirect to="/sign-in" />
        );
      }}
    />
  );
};

export default PrivateRoute;
Enter fullscreen mode Exit fullscreen mode

就是这样。现在,如果用户已登录并刷新页面,他将停留在面板上,而不会重定向到登录页面。但是,如果用户已注销,他只能通过登录页面访问面板。

为什么我们在上下文中使用了loading对象?因为我们在上下文中使用的setAuth
函数是异步的,这意味着它需要一些时间才能真正更新状态。如果没有loading对象,auth.data会在几毫秒内为null。因此,我们在上下文中将loading设置为false,并在PrivateRoute组件中返回所需的路由。

文章来源:https://dev.to/ksushiva/authentication-with-react-js-9e4
PREV
RN 屏幕转换:创建一个转换为屏幕的按钮让我们开始屏幕变化动画按钮动画将 AddButton 放在 iPhone 10+ 的标签栏插入中
NEXT
Git hook 是 Husky 的绝佳替代品