用React路由建立一个安全的SPA教程

318 阅读6分钟

当用React构建SPA(单页面应用程序)时,路由是开发者必须处理的基本流程之一。React路由是建立路由的过程,确定路由的内容,并在认证和授权下确保其安全。有许多工具可用于管理和保护React中的路由。最常用的是react-router 。然而,许多开发人员不在可以使用react-router 库的情况下。正因为如此,他们可能需要使用Reach Router、Wouter,甚至可能根本就没有路由器。

本教程将告诉你如何使用React、Okta和Wouter快速构建一个安全的SPA。Okta可以让你轻松地管理对SPA(或任何应用程序)的访问。通过使用@okta/okta-react 库,你可以用React快速构建安全应用程序。在写这篇文章的时候,reach-router不支持React 17+版本

先决条件:

目录

创建一个Okta OIDC应用程序

在你开始之前,你需要一个免费的Okta开发者账户。安装Okta CLI并运行okta register 来注册一个新账户。如果您已经有一个账户,运行okta login 。然后,运行okta apps create 。选择默认的应用程序名称,或根据你的需要进行更改。 选择单页应用程序,然后按回车键

在重定向URI中使用http://localhost:3000/callback ,并将注销重定向URI设置为http://localhost:3000

Okta CLI是做什么的?

Okta CLI将在您的Okta机构中创建一个OIDC单页应用。它将添加您指定的重定向URI并授予Everyone组的访问权限。它还会为http://localhost:3000 添加一个受信任的来源。当它完成时,你会看到如下的输出。

Okta application configuration:
Issuer:    https://dev-133337.okta.com/oauth2/default
Client ID: 0oab8eb55Kb9jdMIr5d6

注意:你也可以使用Okta管理控制台来创建你的应用程序。更多信息请参见创建一个React应用程序

一旦Okta处理完您的请求,它将返回一个签发人和一个客户ID。确保你注意到这些,因为你将在你的应用程序中需要它们。

创建一个具有路由功能的React应用程序

接下来,打开你最喜欢的IDE(我使用Visual Studio Code),并使用任务运行器npx create-react-app react-routing-demo ,为你的React项目提供支架。你会被提示安装create-react-app 。键入y来批准。

这个过程需要一分钟,如果你愿意,可以拿杯咖啡或茶。任务运行器在快速启动你的应用程序方面做得很好,但你将需要添加和编辑一些文件。

在你开始之前,你将需要安装一些软件包。

首先,当然是wouter。

cd react-routing-demo
npm i wouter@2.7.5

你将使用Bootstrap以及react-bootstrap库来设计你的应用程序。

npm i bootstrap@5.1.3
npm i react-bootstrap@1.6.4

接下来,你将得到dotenv,作为存储Okta敏感信息的一种方式。

npm i dotenv@10.0.0

最后,你将把Okta React SDK添加到你的项目中。

npm i @okta/okta-react@6.2.0 @okta/okta-auth-js@5.6.0

安装了这些依赖项后,你可以开始创建你的应用程序。首先,在你的项目根部添加一个名为.env 的新文件,并添加以下项目。注意这里的REACT_APP_OKTA_ISSUER 应该与Okta CLI中的发行者相匹配。

REACT_APP_OKTA_CLIENTID={yourClientId}
REACT_APP_OKTA_APP_BASE_URL=http://localhost:3000
REACT_APP_OKTA_ISSUER=/oauth2/default

接下来,为ComponentsPages 添加一个文件夹。

mkdir src/Components
mkdir src/Pages

Components ,为Header.jsx 添加一个新文件,并添加以下代码。

import React from "react";
import { Navbar, Nav, Form, Button } from "react-bootstrap";

const Header = ({ authState, oktaAuth }) => {
  if (authState?.isPending) {
    return <div>Loading...</div>;
  }

  const button = authState?.isAuthenticated ? (
    <Button
      variant="secondary"
      onClick={() => {
        oktaAuth.signOut("/");
      }}
    >
      Logout
    </Button>
  ) : (
    <Button
      variant="secondary"
      onClick={() => {
        oktaAuth.signInWithRedirect();
      }}
    >
      Login
    </Button>
  );

  return (
    <Navbar bg="light" expand="lg">
      <Navbar.Brand href="/">React Routing</Navbar.Brand>
      <Navbar.Toggle aria-controls="basic-navbar-nav" />
      <Navbar.Collapse id="basic-navbar-nav">
        <Nav className="mr-auto"></Nav>
        <Form inline>{button}</Form>
      </Navbar.Collapse>
    </Navbar>
  );
};
export default Header;

这个组件为你的用户显示一个登录按钮。一旦用户通过认证,这个按钮就会变成一个注销按钮。该组件还提供了一个访问你将创建的Profile 页面的地方。

Profile.jsx 加入到Pages 文件夹中。这个文件的代码如下。

import React, { useEffect } from "react";
import { Container } from "react-bootstrap";

import { useOktaAuth } from "@okta/okta-react";

import Header from "../Components/Header";

const Profile = () => {
  const { authState, oktaAuth } = useOktaAuth();

  useEffect(() => {
    async function authenticate() {
      if (!authState) return;

      if (!authState.isAuthenticated) {
        await oktaAuth.signInWithRedirect();
      }
    }

    authenticate();
  }, [authState, oktaAuth]);

  if (!authState?.isAuthenticated) {
    return (
      <Container>
        <p>Please wait while we sign you in</p>
      </Container>
    );
  } else {
    return (
      <Container>
        <Header authState={authState} oktaAuth={oktaAuth}></Header>
        <h4>Your profile page</h4>

        <p>Welcome to your profile page </p>
      </Container>
    );
  }
};

export default Profile;

这个页面利用useOktaAuth 钩子来确定用户是否已经登录。如果用户没有登录,那么你将提示他们用Okta登录。否则,你将显示一个简短的个人资料页面。

最后,在Pages 文件夹中添加Home.jsx ,代码如下。

import React from "react";
import { Link, Redirect } from "wouter";

import Header from "../Components/Header";

import { Container, Row, Col, Card } from "react-bootstrap";
import { useOktaAuth } from "@okta/okta-react";

const Home = () => {
  const { authState, oktaAuth } = useOktaAuth();

  return authState?.isAuthenticated ? (
    <Redirect to="/Profile" />
  ) : (
    <Container>
      <Header authState={authState} oktaAuth={oktaAuth}></Header>

      <Row>
        <Col sm={12} className="text-center">
          <h3>React routing Demo</h3>

          <h5>
            A <a href="https://reactjs.org/">React</a> Demo using{" "}
            <a href="https://github.com/molefrog/wouter">Wouter</a> Secured by{" "}
            <a href="https://www.okta.com/">Okta</a>
          </h5>

          <p>
            A tutorial written by{" "}
            <a href="https://profile.fishbowlllc.com">Nik Fisher</a>
          </p>
        </Col>
      </Row>

      <br></br>

      <Row>
        <Col sm={12} className="text-center">
          <Card style={{ width: "21.5em", margin: "0 auto" }}>
            <Card.Header>Already have an Okta Account?</Card.Header>
            <Card.Body>
              <Link to="Profile">Login Here</Link>
            </Card.Body>
          </Card>
        </Col>
      </Row>
    </Container>
  );
};

export default Home;

这个页面使用来自wouter的Redirect 组件,结合useOktaAuth 钩子,将认证的用户重定向到个人资料页面。该页面也作为一个登陆页面,提供一些关于你的应用程序的额外信息。

最后,你将需要用以下代码更新你的App.js 文件。

import React from "react";
import "./App.css";

import { Router, Route } from "wouter";

import { Security, LoginCallback } from "@okta/okta-react";
import { OktaAuth, toRelativeUrl } from "@okta/okta-auth-js";

import Home from "./Pages/Home";
import Profile from "./Pages/Profile";

import "bootstrap/dist/css/bootstrap.min.css";

const issuer = process.env.REACT_APP_OKTA_ISSUER;
const clientId = process.env.REACT_APP_OKTA_CLIENTID;
const redirect = process.env.REACT_APP_OKTA_APP_BASE_URL + "/callback";

class App extends React.Component {
  constructor(props) {
    super(props);

    this.oktaAuth = new OktaAuth({
      issuer: issuer,
      clientId: clientId,
      redirectUri: redirect,
    });

    this.restoreOriginalUri = async (_oktaAuth, originalUri) => {
      window.location.replace(
        toRelativeUrl(originalUri || "/", window.location.origin)
      );
    };
  }

  render() {
    return (
      <Router>
        <Security
          oktaAuth={this.oktaAuth}
          restoreOriginalUri={this.restoreOriginalUri}
        >
          <Route path="/" component={Home} />
          <Route path="/callback" component={LoginCallback} />
          <Route path="/Profile" component={Profile} />
        </Security>
      </Router>
    );
  }
}

export default App;

6.0版的@okta/okta-react 库包括一些需要注意的变化,这取决于你最近使用的软件包的版本。

首先,你必须在<Security> 组件中提供restoreOriginalUri 属性。如果你以前使用的是4.x版本,你会注意到你需要将OktaAuth 对象注入到Security 组件中,而不是发行人、客户ID和其他信息。你可以在Okta的React GitHub页面上阅读更多关于这些变化的信息。

这里你也在导入bootstrap CSS。

最后,你通过在Okta的Security 组件中包裹wouterRouter 来设置路由。这将使你的路由能够访问Okta的API。

运行你的React应用程序

你的应用程序现在已经完成了。你可以使用npm start ,并导航到http://localhost:3000 来运行它。在那里,你点击登录按钮,这将显示Okta登录屏幕。一旦你登录成功,你就会被重定向到Profile 页面。

React routing demo