[译] 用 Auth0 保证 React 应用安全

729 阅读6分钟

原文:auth0.com/blog/how-to…

Auth0

Auth0 是一个全球领先的 Identity-as-a-Service (IDaaS) 服务商,为数以千计的企业客户提供现代身份认证解决方案。除了经典的 “用户名密码认证过程” 外,Auth0 也允许你增加诸如 “社交媒体登录”“多因子认证”“无密码登录” 等等特性,所有这些只需要一些点击就能完成。

用 Auth0 保证 React 应用安全是十分简单方便的。

要完成本文说明的内容,你需要一个 Auth0 账号。如果你还没有,现在是个 注册免费 Auth0 账户 (auth0.com/signup) 的好时机。

同时,如果你想在一个干净的环境中完成本章节内容,你能通过一条命令轻易创建一个 React 应用:

npx create-react-app react-auth0

然后,进入创建好的 react-auth0 目录,就可以按下面的步骤开发了。

设立一个 Auth0 应用

要为你的 React 应用赋予一个 Auth0 账户,你需要创建一个 Auth0 Application。所以,根据 manage.auth0.com/#/applicati… 的描述做如下操作:

  1. 点击 Create Application 按钮
  2. 为你的新应用定义一个 Name (如 "React Demo")
  3. 选择 Single Page Web Applications 作为其类型
  4. 点击 Create 按钮完成这个过程

在创建应用之后,Auth0 会将你重定向到其 Quick Start tab 页中。你得点击到 Settings tab 页去设置一些白名单 URL 以供 Auth0 在认证过程后调用。这是一项 Auth0 实现的安全性措施,用以避免敏感数据泄露(如 ID Tokens)。

所以,当你到达 Settings tab 页时,寻找到 Allowed Callback URLs 并在其中增加 http://localhost:3000/callback。在本教程中,这个简单的 URL 就足够了。

好了!从 Auth0 的视角看,你已经开始很好的保证你的 React 应用的安全了。

依赖和设置

要用 Auth0 保证 React 应用安全,只有三项依赖需要安装:

  • auth0.js
  • react-router
  • react-router-dom

要安装这些依赖,到项目根目录下面执行如下的命令:

npm install --save auth0-js react-router react-router-dom

注意: 如果你想要可获得的最佳安全性,应该依照 auth0.com/docs/univer… 上的说明进行。该方法包括了重定向用户到一个托管在 Auth0 网站上的登录页面,该页面通过 你的 Auth0 dashboard (manage.auth0.com/) 可以方便快捷地定制化。如果你想要更多学习这种最佳实践,可参阅 auth0.com/docs/guides… 页面。

安装好这三个库之后,你就可以创建一个服务来处理认证过程了。可以将该服务叫做 Auth 并用如下代码将其创建到 src/Auth/ 目录:

// src/Auth/Auth.js
import auth0 from 'auth0-js';

export default class Auth {
  constructor() {
    this.auth0 = new auth0.WebAuth({
      // 必须更新以下三行!
      domain: '<AUTH0_DOMAIN>',
      audience: 'https://<AUTH0_DOMAIN>/userinfo',
      clientID: '<AUTH0_CLIENT_ID>',
      redirectUri: 'http://localhost:3000/callback',
      responseType: 'token id_token',
      scope: 'openid profile'
    });

    this.getProfile = this.getProfile.bind(this);
    this.handleAuthentication = this.handleAuthentication.bind(this);
    this.isAuthenticated = this.isAuthenticated.bind(this);
    this.login = this.login.bind(this);
    this.logout = this.logout.bind(this);
    this.setSession = this.setSession.bind(this);
  }

  getProfile() {
    return this.profile;
  }

  handleAuthentication() {
    return new Promise((resolve, reject) => {
      this.auth0.parseHash((err, authResult) => {
        if (err) return reject(err);
        console.log(authResult);
        if (!authResult || !authResult.idToken) {
          return reject(err);
        }
        this.setSession(authResult);
        resolve();
      });
    })
  }

  isAuthenticated() {
    return new Date().getTime() < this.expiresAt;
  }

  login() {
    this.auth0.authorize();
  }

  logout() {
    // 清除 id token 和过期时间
    this.idToken = null;
    this.expiresAt = null;
  }

  setSession(authResult) {
    this.idToken = authResult.idToken;
    this.profile = authResult.idTokenPayload;
    // 设置 id token 的过期时间
    this.expiresAt = authResult.expiresIn * 1000 + new Date().getTime();
  }
}

你刚刚创建的这个 Auth 服务包含了用于处理登入、登出不同步骤的各种函数。下面的列表概述了这些函数:

  • getProfile: 返回已登录用户的 profile
  • handleAuthentication: 查找 URL hash 中的认证过程结果。然后,该函数用 auth0-js 中的 parseHash 方法处理结果
  • isAuthenticated: 检查用户 ID token 是否过期
  • login: 初始化登录过程,将用户重定向到登录页面
  • logout: 清除用户的 tokens 和过期时间
  • setSession: 设置用户的 ID token、profile 及过期时间

除了这些函数,该类还包含了一个名为 auth0 的属性,用来从你的 Auth0 应用中提取初始化值。同时记住你 必须 替换掉其中的 <AUTH0_DOMAIN><AUTH0_CLIENT_ID> 占位符是重要的。

注意: 对于 <AUTH0_DOMAIN> 占位符,你得将它替换成类似 your-subdomain.auth0.com 的形式,其中 your-subdomain 是你在创建 Auth0 账户(或你的 Auth0 租户名, auth0.com/docs/gettin…)时选择的子域名。而对于 <AUTH0_CLIENT_ID>,需要将其替换为从你之前创建的 Auth0 应用中 Client ID 域中拷贝的随机字符串。

由于使用了 Auth0 登录页面,用户会被带离你的应用。不过,在其认证过后,又会被自动带回到你之前设置过的回调 URL 上 (也就是 http://localhost:3000/callback)。这意味着你需要创建一个组件来负责这个路由。

所以,创建 src/Callback 目录并在其中创建一个叫做 Callback.js 的文件,插入如下的代码:

// src/Callback/Callback.js
import React from 'react';
import { withRouter } from 'react-router';

function Callback(props) {
  props.auth.handleAuthentication().then(() => {
    props.history.push('/');
  });

  return (
    <div>
      Loading user profile.
    </div>
  );
}

export default withRouter(Callback);

这个组件,正如你所见,负责触发 handleAuthentication 过程,并在该过程结束时将用户带入主页。而当该组件处理认证结果的过程中,只是简单的显示了 “loading the user profile”

AuthCallback 组件都创建完毕,就可以重构 App 组件以整合所有事情了:

// src/App.js

import React from 'react';
import {withRouter} from 'react-router';
import {Route} from 'react-router-dom';
import Callback from './Callback/Callback';
import './App.css';

function HomePage(props) {
  const {authenticated} = props;

  const logout = () => {
    props.auth.logout();
    props.history.push('/');
  };

  if (authenticated) {
    const {name} = props.auth.getProfile();
    return (
      <div>
        <h1>Howdy! Glad to see you back, {name}.</h1>
        <button onClick={logout}>Log out</button>
      </div>
    );
  }

  return (
    <div>
      <h1>I don't know you. Please, log in.</h1>
      <button onClick={props.auth.login}>Log in</button>
    </div>
  );
}

function App(props) {
  const authenticated = props.auth.isAuthenticated();

  return (
    <div className="App">
      <Route exact path='/callback' render={() => (
        <Callback auth={props.auth}/>
      )}/>
      <Route exact path='/' render={() => (
        <HomePage
          authenticated={authenticated}
          auth={props.auth}
          history={props.history}
        />)
      }/>
    </div>
  );
}

export default withRouter(App);

在本例中,实际上你在一个文件中定义了两个组件(就是为了简单)。首先定义一个 HomePage 组件展示已登录用户名的信息,以及告知未登录用户去登录的信息。同时,文件中的 App 组件负责决定根据路由哪些子组件必须渲染。

要注意你在所有组件中(AppHomePageCallback)都用到了 Auth 服务。因此你需要这个服务的一个全局实例,并且将其包含在 App 组件中。

所以,要创建这个全局 Auth 实例并整合到应用中,需要更新 index.js 文件:

// src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Auth from './Auth/Auth';
import './index.css';
import App from './App';

const auth = new Auth();

ReactDOM.render(
  <BrowserRouter>
    <App auth={auth} />
  </BrowserRouter>,
  document.getElementById('root')
);

这样就完成了!你已经用 Auth0 保护了你的 React 应用。如果用 npm start 启动了应用,你将能够借助 Auth0 的帮助自己实现认证了,也能看到 React 应用显示了你的名字(如果你的身份提供者确实提供了一个名字的话)。

如果你想学习更多的话,Auth0 官方文档中也提供了各种前端框架的整合方法:

https://auth0.com/docs/quickstart/spa

扩展阅读



--End--

查看更多前端好文
请搜索 fewelife 关注公众号

转载请注明出处