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… 的描述做如下操作:
- 点击 Create Application 按钮
- 为你的新应用定义一个 Name (如 "React Demo")
- 选择 Single Page Web Applications 作为其类型
- 点击 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
: 返回已登录用户的 profilehandleAuthentication
: 查找 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” 。
当 Auth
和 Callback
组件都创建完毕,就可以重构 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
组件负责决定根据路由哪些子组件必须渲染。
要注意你在所有组件中(App
、HomePage
和 Callback
)都用到了 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