React 身份验证,简化

2025-05-28

React 身份验证,简化

身份验证是那些似乎总是需要比我们想要的付出更多努力的事情之一。

要设置身份验证,您必须重新研究自上次进行身份验证以来从未考虑过的主题,而且该领域的快速发展意味着在此期间情况经常发生变化。新的威胁、新的选项和新的更新可能让您在过去的项目中不断猜测和查阅文档。

在本文中,我们将介绍 React 应用程序中一种不同的身份验证方法(以及访问控制和单点登录)。我们无需添加一个静态库,也无需每次实现身份验证时都保持更新或重新研究,而是使用一种可以自动更新的服务,它比 Auth0、Okta 和其他方案更简单。

反应身份验证

在 React 中编写身份验证时,我们通常使用类似的方法:我们的 React 应用向身份验证服务器发出请求,然后服务器返回一个访问令牌。该令牌保存在浏览器中,可用于后续向您的服务器(或根据需要向其他服务器)发出的请求。无论是编写标准的电子邮件地址和密码身份验证,还是使用魔术链接或像 Google、Azure 或 Facebook 这样的单点登录 (SSO) 登录,我们都希望 React 应用能够向身份验证服务器发送初始请求,并让该服务器处理生成令牌的所有复杂过程。

因此,React 在身份验证中的职责是:

  1. 向认证服务器发送初始请求
  2. 接收并存储访问令牌
  3. 每次后续请求时将访问令牌发送到您的服务器

JWT 访问令牌

JSON Web Tokens(JWT)是紧凑的、URL 安全的令牌,可用于 React 应用程序中的身份验证和访问控制。每个 JWT 都包含一个简单的 JSON 对象作为其“有效负载”,并经过签名,以便服务器可以验证其真实性。JWT 的示例如下所示:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9.f7iKN-xi24qrQ5NQtOe0jiriotT-rve3ru6sskbQXnA
Enter fullscreen mode Exit fullscreen mode

此令牌的有效载荷是中间部分(以句点分隔):

eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9
Enter fullscreen mode Exit fullscreen mode

JWT 有效负载可以通过 base64 解码生成 JSON 对象:

JSON.parse(atob("eyJ1c2VySWQiOjEsImF1dGhvcml6YXRpb24iOiJhZG1pbiJ9"));

// =>
{
  userId: 1,
  authorization: admin
}
Enter fullscreen mode Exit fullscreen mode

值得注意的是,任何拥有 JWT 的人都可以读取此有效负载,包括您的 React 应用程序或第三方。

任何拥有 JWT 的人都可以读取其内容。但是,只有身份验证服务器才能生成有效的 JWT——您的 React 应用程序、应用服务器或恶意第三方都无法生成有效的 JWT。因此,除了读取 JWT 之外,您的服务器还需要通过与公钥进行校验来验证 JWT 的真实性。这允许您的应用服务器验证传入的 JWT,并拒绝任何非由身份验证服务器创建的令牌或已过期的令牌。

在 React 应用程序中使用 JWT 的流程如下:

  1. 每当用户想要登录时,您的 React 应用程序都会请求 JWT。
  2. 身份验证服务器使用私钥生成 JWT,然后将 JWT 发送回您的 React 应用程序。
  3. 您的 React 应用程序会存储此 JWT,并在用户需要发出请求时将其发送到您的应用程序服务器。
  4. 您的应用程序服务器使用公钥验证 JWT,然后读取有效负载以确定哪个用户正在发出请求。

这些步骤写下来都很简单,但实际操作起来却各有隐患,需要谨慎操作才能确保安全。尤其随着时间的推移,新的威胁向量不断涌现,新的平台需要修补或支持,安全开销会迅速增加。

Userfront 消除了 React 应用中身份验证的复杂性

Userfront 是一个抽象化身份验证复杂性的框架。这使得您在 React 应用程序中处理身份验证变得更加轻松,或许最重要的是,它会随着时间的推移自动更新所有身份验证协议。

Userfront 的核心理念是:世界一流的身份验证无需费力——它应该易于设置,并且安全更新应该自动完成。Userfront 拥有身份验证、单点登录 (SSO)、访问控制和多租户等所有功能,并提供可立即投入生产的免费套餐,每月最多可容纳 10,000 名活跃用户。对于大多数现代 React 应用程序来说,这是一个很好的解决方案。

在 React 中设置身份验证

现在我们将介绍如何在 React 应用程序中构建身份验证的所有主要方面。此示例的最终代码可在此处获取。

使用你最喜欢的样板代码来设置你的 React 应用程序,并让你的构建管道井然有序。在本文中,我们将使用Create React App,它为我们完成了大量的设置工作,并且我们还将添加React Router用于客户端路由。首先安装 Create React App 和 React Router:

npx create-react-app my-app
cd my-app
npm install react-router-dom --save
npm start
Enter fullscreen mode Exit fullscreen mode

现在我们的 React 应用程序可以在http://localhost:3000上访问

创建 React App 身份验证

正如它所说的,我们现在可以编辑src/App.js文件来开始工作。

src/App.js根据 React Router 快速入门,将内容替换为以下内容:

// src/App.js

import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";

export default function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
            <li>
              <Link to="/reset">Reset</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

        <Switch>
          <Route path="/login">
            <Login />
          </Route>
          <Route path="/reset">
            <PasswordReset />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

function Home() {
  return <h2>Home</h2>;
}

function Login() {
  return <h2>Login</h2>;
}

function PasswordReset() {
  return <h2>Password Reset</h2>;
}

function Dashboard() {
  return <h2>Dashboard</h2>;
}
Enter fullscreen mode Exit fullscreen mode

现在我们有一个非常简单的带有路由的应用程序:

路线 描述
/ 主页
/login 登录页面
/reset 密码重置页面
/dashboard 用户仪表板,仅供登录用户使用

React Router 身份验证

这就是我们开始添加身份验证所需的全部结构。

使用 Userfront 注册、登录和重置密码

首先,在https://userfront.com创建一个 Userfront 帐户。这将为您提供注册表单、登录表单和密码重置表单,您可以在后续步骤中使用它们。

在 Userfront 仪表板的工具包部分中,您可以找到安装注册表单的说明:

用户端安装说明

按照说明安装 Userfront React 包:

npm install @userfront/react --save
npm start
Enter fullscreen mode Exit fullscreen mode

然后通过导入和初始化 Userfront 将表单添加到您的主页,然后更新Home()函数以呈现表单。

// src/App.js

import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Userfront from "@userfront/react";

Userfront.init("demo1234");

const SignupForm = Userfront.build({
  toolId: "nkmbbm",
});

export default function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
            <li>
              <Link to="/reset">Reset</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

        <Switch>
          <Route path="/login">
            <Login />
          </Route>
          <Route path="/reset">
            <PasswordReset />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
      <SignupForm />
    </div>
  );
}

function Login() {
  return <h2>Login</h2>;
}

function PasswordReset() {
  return <h2>Password Reset</h2>;
}

function Dashboard() {
  return <h2>Dashboard</h2>;
}
Enter fullscreen mode Exit fullscreen mode

现在主页上已经有了你的注册表单。尝试注册一个用户:

React 注册表单

该表单默认处于“测试模式”,它将在测试环境中创建用户记录,您可以在 Userfront 仪表板中单独查看:

用户前端测试模式

继续按照添加注册表单的相同方式添加登录名和密码重置表单:

// src/App.js

import React from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import Userfront from "@userfront/react";

Userfront.init("demo1234");

const SignupForm = Userfront.build({
  toolId: "nkmbbm",
});
const LoginForm = Userfront.build({
  toolId: "alnkkd",
});
const PasswordResetForm = Userfront.build({
  toolId: "dkbmmo",
});

export default function App() {
  return (
    <Router>
      <div>
        <nav>
          <ul>
            <li>
              <Link to="/">Home</Link>
            </li>
            <li>
              <Link to="/login">Login</Link>
            </li>
            <li>
              <Link to="/reset">Reset</Link>
            </li>
            <li>
              <Link to="/dashboard">Dashboard</Link>
            </li>
          </ul>
        </nav>

        <Switch>
          <Route path="/login">
            <Login />
          </Route>
          <Route path="/reset">
            <PasswordReset />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </div>
    </Router>
  );
}

function Home() {
  return (
    <div>
      <h2>Home</h2>
      <SignupForm />
    </div>
  );
}

function Login() {
  return (
    <div>
      <h2>Login</h2>
      <LoginForm />
    </div>
  );
}

function PasswordReset() {
  return (
    <div>
      <h2>Password Reset</h2>
      <PasswordResetForm />
    </div>
  );
}

function Dashboard() {
  return <h2>Dashboard</h2>;
}
Enter fullscreen mode Exit fullscreen mode

此时,您的注册、登录和密码重置都应该可以正常使用了。

您的用户可以注册、登录和重置密码。

React 注册、登录、密码重置

React 中的受保护路由

通常,我们不希望用户在未登录的情况下能够查看仪表板。这称为保护路线。

每当用户未登录但尝试访问时/dashboard,我们都可以将他们重定向到登录屏幕。

我们可以通过更新Dashboard组件来src/App.js处理条件逻辑来实现这一点。

当用户使用 Userfront 登录时,他们将获得一个可用的访问令牌Userfront.accessToken()。我们可以检查此令牌来确定用户是否已登录。

将组件添加到 React Router 的语句Redirectimport,然后更新Dashboard组件以在不存在访问令牌时重定向。

// src/App.js

import React from "react";
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link,
  Redirect, // Be sure to add this import
} from "react-router-dom";

// ...

function Dashboard() {
  function renderFn({ location }) {
    // If the user is not logged in, redirect to login
    if (!Userfront.accessToken()) {
      return (
        <Redirect
          to={{
            pathname: "/login",
            state: { from: location },
          }}
        />
      );
    }

    // If the user is logged in, show the dashboard
    const userData = JSON.stringify(Userfront.user, null, 2);
    return (
      <div>
        <h2>Dashboard</h2>
        <pre>{userData}</pre>
        <button onClick={Userfront.logout}>Logout</button>
      </div>
    );
  }

  return <Route render={renderFn} />;
}
Enter fullscreen mode Exit fullscreen mode

还要注意,我们通过Userfront.logout()直接调用添加了一个注销按钮:

<button onClick={Userfront.logout}>Logout</button>
Enter fullscreen mode Exit fullscreen mode

现在,当用户登录后,他们可以查看仪表板。如果用户未登录,他们将被重定向到登录页面。

React 受保护的路线

使用 API 进行反应身份验证

您可能希望从后端检索用户特定的信息。为了保护这些 API 端点,您的服务器应该检查传入的 JWT 是否有效。

有许多库可用于跨各种语言读取和验证 JWT;以下是一些用于处理 JWT 的流行库:

对于 Userfront,访问令牌在您的 React 应用程序中可用Userfront.accessToken()

你的 React 应用程序可以将其作为Bearertoken 发送到 header 中Authorization。例如:

// Example of calling an endpoint with a JWT

async function getInfo() {
  const res = await window.fetch("/your-endpoint", {
    method: "GET",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${Userfront.accessToken()}`,
    },
  });

  console.log(res);
}

getInfo();
Enter fullscreen mode Exit fullscreen mode

要处理这样的请求,您的后端应该从Authorization标头中读取 JWT,并使用在 Userfront 仪表板中找到的公钥验证其是否有效。

下面是一个 Node.js 中间件读取和验证 JWT 的示例:

// Node.js example (Express.js)

const jwt = require("jsonwebtoken");

function authenticateToken(req, res, next) {
  // Read the JWT access token from the request header
  const authHeader = req.headers["authorization"];
  const token = authHeader && authHeader.split(" ")[1];
  if (token == null) return res.sendStatus(401); // Return 401 if no token

  // Verify the token using the Userfront public key
  jwt.verify(token, process.env.USERFRONT_PUBLIC_KEY, (err, auth) => {
    if (err) return res.sendStatus(403); // Return 403 if there is an error verifying
    req.auth = auth;
    next();
  });
}
Enter fullscreen mode Exit fullscreen mode

使用这种方法,任何无效或缺失的令牌都会被服务器拒绝。您也可以稍后在路由处理程序中使用以下req.auth对象引用令牌的内容:

console.log(req.auth);

// =>
{
  mode: 'test',
  tenantId: 'demo1234',
  userId: 1,
  userUuid: 'ab53dbdc-bb1a-4d4d-9edf-683a6ca3f609',
  isConfirmed: false,
  authorization: {
    demo1234: {
      tenantId: 'demo1234',
      name: 'Demo project',
      roles: ["admin"],
      permissions: []
    },
  },
  sessionId: '35d0bf4a-912c-4429-9886-cd65a4844a4f',
  iat: 1614114057,
  exp: 1616706057
}
Enter fullscreen mode Exit fullscreen mode

有了这些信息,您可以根据需要执行进一步的检查,或者使用userIduserUuid查找要返回的用户信息。

例如,如果您想要将路由限制为管理员用户,您可以authorization根据已验证的访问令牌的对象进行检查:

// Node.js example (Express.js)

app.get("/users", (req, res) => {
  const authorization = req.auth.authorization["demo1234"] || {};

  if (authorization.roles.includes("admin")) {
    // Allow access
  } else {
    // Deny access
  }
});
Enter fullscreen mode Exit fullscreen mode

React SSO(单点登录)

从这里,您可以将社交身份提供商(如 Google、Facebook 和 LinkedIn)添加到您的 React 应用程序,或将业务身份提供商(如 Azure AD、Office365 等)添加到您的 React 应用程序。

您可以使用身份提供商(例如 Google)创建一个应用程序,然后将该应用程序的凭据添加到 Userfront 仪表板。最终结果是修改后的登录体验:

React SSO 表单

使用此方法不需要额外的代码来实现单点登录:您可以添加和删除提供程序,而无需更新表单或处理 JWT 的方式。

最后说明

为 React 应用添加身份验证和访问控制并非易事。无论是设置步骤,还是更重要的后续维护,都可以使用Userfront等现代平台来处理。

JSON Web Tokens 允许您将身份验证令牌生成层与应用程序的其余部分清晰地分离,使其更易于推理,并更模块化,以满足未来的需求。这种架构还允许您将精力集中在核心应用程序上,在那里您可以为自己或客户创造更多价值。

有关向 React 应用程序添加身份验证的更多详细信息,请访问Userfront 指南,其中涵盖了从设置身份验证表单到 API 文档、示例存储库、使用不同语言和框架等所有内容。

创建一个免费的 Userfront 项目

文章来源:https://dev.to/tyrw/react-authentication-simplified-18gl
PREV
15+ 个值得你考虑用于项目的精彩 React UI 库
NEXT
2018 年 React.js 综合指南