使用 Auth0 对 React 应用程序进行身份验证

已发表: 2022-03-10
快速总结↬应用程序开发的一个重要方面是确保只有经过验证的用户才能访问我们的应用程序。 这可能既乏味又昂贵,尤其是当您添加登录外部电子邮件和密码的替代方法时。 Auth0 是一种为开发人员提供开箱即用的身份验证功能的服务。

在本文中,我们将学习如何使用 Auth0 对我们的 React 应用程序进行身份验证。 我们还将学习如何在我们的应用程序中设置社交登录。 这篇文章对想要在他们的应用程序中添加某种形式的身份验证或想要熟悉 Auth0 的读者很有帮助。

身份验证是大多数应用程序的一个关键方面,因为开发人员必须确保他们构建的应用程序是安全的,并且只能由经过验证的用户访问。 虽然可以构建自定义身份验证解决方案,但构建、维护、托管和保护它们所涉及的成本和资源可能很沉重。 这就是 Auth0 的用武之地。

Auth0 为所有流行的 Web、移动和原生平台提供 SDK,允许与您偏好的语言和堆栈进行深度集成。 您还可以设置不同的登录选项,以便您的用户可以使用他们喜欢的方法登录您的应用程序。

本文不深入解释身份验证如何在幕后工作。 Auth0 有一个资源涵盖了这一点。

注意:要继续学习,您需要对 React 和 React Hooks 有基本的了解。

什么是 Auth0?

Auth0 是一种灵活的解决方案,可为您的应用添加身份验证和授权。 您可以将任何应用程序连接到 Auth0 并定义您想要使用的身份提供者,无论是 Google、Facebook、Github 还是其他。 每当用户登录您的应用程序时,Auth0 都会验证他们的身份并将身份验证数据发送回您的应用程序。

虽然 Auth0 带有不同的登录表单,但它们的通用登录是最安全、上手速度最快的。 Auth0 还建议您使用它。 使用通用登录,用户被重定向到登录页面,由 Auth0 的服务器进行身份验证,然后他们被重定向回您的应用程序。 使用通用登录时,您可以使用简单的用户名和密码开始,然后根据您的应用要求添加其他登录方式。

使用通用登录的另一个好处是您不需要设置自定义登录页面。 但是,您可以自定义通用登录以满足您的需要。

跳跃后更多! 继续往下看↓

Auth0 如何工作?

当 Auth0 的服务器将用户重定向回您的应用程序时,重定向 URL 会填充有关经过身份验证的用户的信息。 这使我们能够从我们从身份提供者那里获得的信息中访问有关用户的数据。 Auth0 中的用户配置文件是从身份提供者获得的信息。 我们取回的用户数据将因身份提供者而异。

当用户被重定向回应用程序时,重定向 URL 中发送的信息如下:

  • 访问令牌
    这用于通知 API 令牌持有者有权访问 API 并执行某些操作。 访问令牌不用于携带有关用户的信息。 它们仅用于授权对资源的访问。
  • 标识令牌
    这是由 OpenID 提供者授予的安全令牌,其中包含有关用户的信息。 此信息告诉您的客户端应用程序用户已通过身份验证,并且还可以为您提供用户名等信息。 它采用 JSON Web Token (JWT) 格式。
  • 过期日期在
    这告诉我们访问令牌不再有效还有多少秒。 默认情况下,这是 1200 秒(20 分钟)。 当访问令牌过期时,应用程序将被强制让用户重新登录。
  • 范围
    应用程序在身份验证期间使用 OpenID Connect (OIDC) 范围来授权访问用户的详细信息,例如姓名和图片。 每个范围都返回一组用户属性,称为声明。 应用应请求的范围取决于应用所需的用户属性。 用户授权请求的范围后,声明将在 ID 令牌中返回,并且还可以通过 /userinfo 端点获得。

Auth0 身份验证方法

Auth0 提供了多种平台集成。 在本文中,我们将了解 JavaScript SDK 和 React SDK。

  • JavaScript SDK:这是一个用于 Auth0 API 的客户端 JavaScript 工具包。
  • React SDK:Auth0 React SDK (auth0-react.js) 是一个 JavaScript 库,用于在使用 Auth0 的 React 应用程序中实现身份验证和授权。

配置您的 Auth0 应用程序

  • 在仪表板上创建 Auth0 应用程序。
您的 Auth0 仪表板
您的 Auth0 仪表板。 (大预览)
  • 选择应用类型。 我们的是SPA。
选择应用类型
选择应用类型。 (大预览)
  • 选择技术。
选择技术
选择技术。 (大预览)
  • 记下您的应用凭据。 我们需要他们将 Auth0 集成到我们的 react 应用程序中。
应用凭据
应用凭据。 (大预览)

我们在其设置中配置应用程序的 URL,以便登录和注销功能正常工作。

回调 URL 是您的应用程序中的 URL,Auth0 在用户通过身份验证后将其重定向。 对于我们的应用,将Allowed Callback URL设置为https://localhost:3000

Auth0 将用户从授权服务器注销后,注销 URL 是用户被重定向到的 URL。 我们还将其设置为https://localhost:3000 。 回调 URL 可以被未经授权的各方操纵,因此 Auth0 仅将应用设置的允许回调 URL字段中的 URL 识别为有效。

Allowed Web Origins处理当前身份验证会话的检查。 这可确保用户在离开您的应用程序或刷新页面时保持登录状态。 我们还将其设置为https://localhost:3000

使用 Auth0 JavaScript SDK 进行身份验证

让我们使用这个 SDK 来模拟一个基本的 Auth0 登录流程。 本部分的源代码可在 GitHub 上找到。 此演示应用程序的组件是:

  • App.js :这是根组件。 我们将稍后创建的Auth类从这里传递给每个组件。
  • Nav.js :这将包含登录和注销按钮,帮助用户正确地从一个页面导航到另一个页面。
  • Profile.js :用户配置文件。 只有当用户登录应用程序时才能访问它。
  • Home.js : Home 组件。
  • Auth.js :我们在我们将定义的Auth类中定义身份验证实用程序。
  • Callback.js :组件 Auth0 将用户重定向到他们登录后。

让我们将应用程序的凭据设置为环境变量。

 REACT_APP_AUTH0_DOMAIN=your-domain REACT_APP_AUTH0_CLIENTID=your-client-id REACT_APP_AUTH0_CALLBACK_URL=your-callback-url

创建一个.env来存储应用程序的domaincleintId凭据。 此外,在文件中设置回调 URL。 在这个应用程序中,我将使用 https://localhost:3000 作为我的回调 URL。

添加 Auth0 实例

npm i auth0-js import auth0 from 'auth0-js';

要在我们的应用程序中使用 JavaScript SDK,我们首先安装 SDK。 接下来,我们创建一个Auth.js文件,在其中设置身份验证功能。 auth0auth0-js导入到Auth.js文件中。

 export default class Auth { constructor(history){ this.history = history; this.auth0 = new auth0.WebAuth({ domain: process.env.REACT_APP_AUTH0_DOMAIN, clientID: process.env.REACT_APP_AUTH0_CLIENTID, redirectUri: process.env.REACT_APP_AUTH0_CALLBACK_URL, responseType: "token id_token", scope: "openid profile email" }) }

接下来,我们初始化 Auth0 应用程序的新实例。 为此,请创建一个名为Auth的类。 在这里,我们初始化一个新的 Auth0 实例。 我们传入一个包含一些参数的options对象。

我们可以向 Auth0 实例添加几个参数,在这些参数中,只有domainclientID是必需的。

  • domain :您的 Auth0 帐户域。
  • clientID :您的 Auth0 客户端 ID。
  • redirectUri :当用户通过身份验证时,URL Auth0 会重定向您的用户。 默认情况下,将使用您为应用的回调 URL指定的 URL,因此不需要此参数。
  • responseType :我们定义了当 Auth0 对我们的用户进行身份验证时我们想要从它返回的响应。 我们指定要从响应中id_token
  • scope :我们定义我们想从用户那里得到什么信息。 这样,我们将能够访问他们的电子邮件地址以及他们个人资料中存储的任何信息。 我们能够从用户那里获得的信息取决于他们用于登录的身份提供者。我们将使用 OpenID Connect 协议来访问有关用户的信息。

Auth类接受react-routerhistory道具作为参数。 稍后,我们将使用它来将用户重定向到我们应用程序中的不同页面。

我们创建一个新的auth0实例并传入配置。 我们将新实例分配给this.auth0 。 我们从我们之前创建的.env文件中获取domainclientIDredirectUri的值。

添加登录功能

我们需要在Auth.js中创建的类中添加登录方法。

 login = () => { this.auth0.authorize() }

为此,我们将 Auth0 的authorize()方法添加到loginauthorize()用于通过 Universal Login 登录用户。 当authorize()被调用时,它会将用户重定向到 Auth0 的登录页面。

Auth类需要传递给其他组件,即NavHomeCallback组件。

 import Auth from './Auth'; function App({history}) { const auth = new Auth(history) return ( <div className="App"> <Nav auth={auth}/> <Switch> <div className="body"> <Route exact path="/" render={props => <Home auth={auth} {...props} />} /> <Route exact path="/callback" render={props => <Callback auth={auth} {...props} />} /> <Route exact path="/profile" render={props => <Profile auth={auth} {...props} />} /> </div> </Switch> </div> ); } export default withRouter(App);

在这里,我们创建了一个Auth类的新实例,并将它作为 props 传递给需要它的组件。

由于Auth类需要history ,我们将使用withRouter以便我们能够访问history

 import { Link } from 'react-router-dom' const Nav = ({auth}) => { return ( <nav> <ul> <li><Link to="/">Home</Link></li> <li> <button onClick={auth.login}>log in</button> </li> </ul> </nav> ) } export default Nav

现在我们已经定义了login()方法,我们可以在登录按钮中使用它。 用户将被重定向到 Auth0 的登录页面,然后在通过身份验证后重定向到回调 URL。

接下来,我们必须创建用户登录后被重定向到的组件。

 import React from 'react' const Callback = () => { return ( <div> <h1>I am the callback component</h1> </div> ) } export default Callback

创建一个Callback.js文件,并在其中设置一个Callback组件。 现在,当用户登录时,他们将被重定向到Callback组件。

处理认证

当 Auth0 将用户重定向回应用程序时,它会在回调 URL 中发送一些身份验证数据。 此数据包含有关经过身份验证的用户的编码信息。 为了访问 Auth0 在重定向 URL 中发回的数据,我们在Auth类中设置了一个handleAuth()方法。 该方法将在Callback组件中调用。

 handleAuth = () => { this.auth0.parseHash((err, authResult) => { if(authResult && authResult.accessToken && authResult.idToken) { this.setSession(authResult); this.history.push("/"); } else if (err) { alert(`Error: ${err.error}`) console.log(err); } }) }

用户被重定向后,我们可以使用parseHash方法解析回调 URL 中返回的信息。 解析后,我们得到一个error对象和一个authResult 。 我们检查是否有authResult以及accessTokenidToken 。 如果为真,我们将authResult传递给setSession方法并将用户重定向到主页。

稍后我们将使用setSession()为经过身份验证的用户创建会话,并将身份验证数据存储在本地存储中。 如果有任何错误,我们使用alert方法显示它们并将错误对象记录到控制台。

每当Callback挂载时,即用户在登录后被重定向时,我们都会在useEffect中调用我们上面定义的handleAuth()方法。

 import React, {useEffect} from 'react' const Callback = ({auth}) => { useEffect(() => { auth.handleAuth() }, []) return ( <div> <h1>I am the callback component</h1> </div> ) } export default Callback

我们这样做是因为当 Auth0 将用户重定向到Callback组件时,我们希望能够访问它在重定向 URL 中发送的响应数据,而handleAuth()方法是我们调用 Auth0 的parseHash方法的地方。 所以当组件挂载时,我们在useEffect中调用handleAuth()

跟踪身份验证状态

如果用户尚未登录,我们不希望可以访问profile页面。我们需要能够检查用户是否经过身份验证,然后授予他们访问profile页面的权限。 我们可以利用在Auth类中的handleAuth()方法中调用的setSession()方法。

 setSession = authResult => { //set the time the access token will expire const expiresAt = JSON.stringify( authResult.expiresIn * 1000 + new Date().getTime() ) localStorage.setItem("access_token", authResult.accessToken) localStorage.setItem("id_token", authResult.idToken) localStorage.setItem("expires_at", expiresAt) }

setSession()中,我们添加了一个expiresAt变量来保存访问令牌的过期时间。 expiresIn是一个字符串,包含accessToken的过期时间(以秒为单位)。 我们将从expiresIn得到的过期时间转换为 Unix 纪元时间。 接下来,我们将expiresAt以及authResultaccessTokenidToken到本地存储。

为身份验证状态设置跟踪器的下一步是创建一个isAuthenticated方法。

 isAuthenticated = () => { const expiresAt =JSON.parse(localStorage.getItem("expires_at")); return new Date().getTime() < expiresAt; }

在上面的方法中,我们解析保存到本地存储的expires_at值,并检查当前时间是否小于令牌过期时间。 如果为true ,则用户已通过身份验证。

现在我们可以跟踪isAuthenticated状态,我们可以在我们的应用程序中使用它。 让我们在Nav.js文件中使用它。

 import React from 'react'; import { Link } from 'react-router-dom' const Nav = ({auth}) => { return ( <nav> <ul> <li><Link to="/">Home</Link></li> <li> <button onClick={auth.isAuthenticated() ? auth.logout : auth.login}> {auth.isAuthenticated() ? "log out" : "log in"} </button> </li> </ul> </nav> ) } export default Nav

我们不是硬编码登录按钮并使用login()方法,而是根据isAuthenticated状态动态呈现使用login()方法的登录按钮或使用 logout logout() ) 方法的注销按钮。 在Nav组件中,我们使用三元运算符来确定按钮上显示的文本以及用户单击按钮时调用的方法。 显示的文本和调用的方法取决于auth.isAuthenticated()的值。

现在我们可以继续实现Home组件了。

 import {Link} from 'react-router-dom' const Home = ({auth}) => { return ( <div> <h1>home</h1> { auth.isAuthenticated() && ( <h4> You are logged in! You can now view your{' '} <Link to="/profile">profile</Link> </h4> ) } </div> ) } export default Home

在上面的Home组件中,如果用户登录,我们使用isAuthenticated状态动态显示指向用户个人资料的链接。

我们希望在用户登录应用程序时显示有关用户的信息。 为此,我们必须在Auth类中创建两个获取该信息的方法。

 getAccessToken = () => { const accessToken = localStorage.getItem("access_token") if(!accessToken){ throw new Error("No access token found") } return accessToken }

获取用户数据需要访问令牌。 我们创建一个getAccessToken()方法,从本地存储中获取访问令牌。 如果没有访问令牌,我们会抛出错误。

getProfile()方法为我们获取用户数据,这就是它的样子。

 getProfile = callback => { this.auth0.client.userInfo(this.getAccessToken(), (err, profile) => { callback(profile); }); }

getProfile()方法调用userInfo()方法,该方法将向/userinfo端点发出请求并返回包含用户信息的用户对象。 /userinfo端点需要访问令牌,因此我们将getAccessToken()作为参数传递。

响应中包含的用户配置文件信息取决于我们设置的范围。 早些时候,我们将应用程序的范围设置为profileemail ,因此这些是我们将返回的有关用户的唯一信息。

让我们设置Profile组件。

 import React, { useEffect, useState } from "react"; const Profile = ({ auth }) => { const [profile, setProfile] = useState(null); useEffect(() => { auth.getProfile((profile) => { setProfile(profile); }); }, [auth]); if (!profile) { return <h1>Loading...</h1>; } return ( <div> <h1>profile</h1> <> <p>{profile.name}</p> <p>{profile.nickname}</p> <img src={profile.picture} /> <pre>{JSON.stringify(profile, null, 2)}</pre> </> </div> ); }; export default Profile;

Profile.js中,我们创建一个profile状态,并在useEffect中调用getProfile方法来访问用户的配置文件。 然后我们显示从profile状态获得的用户数据。

添加注销功能

我们在Auth类中定义了一个logout()方法。

 logout = () => { localStorage.removeItem("access_token") localStorage.removeItem("id_token") localStorage.removeItem("expires_at") this.auth0.logout({ clientID: process.env.REACT_APP_AUTH0_CLIENTID, returnTo: "https://localhost:3000" }); }

在这里,我们删除了我们之前存储在本地存储中的authResultaccessTokenidToken 。 然后我们将用户引导到主页。

要从 Auth0 的服务器注销用户,请使用 Auth0 logout()方法。 此方法接受包含clientIDreturnTo属性的选项对象。 returnTo是您在应用程序中指定用户注销后应重定向到的 URL 的位置。 提供的returnTo URL 必须列在 Auth0 仪表板中应用的允许注销 URL中。

使用 React SDK 进行身份验证

与 JavaScript SDK 不同,React SDK 更易于使用。 本部分的代码可在 GitHub 上找到。

让我们在我们的应用程序中进行设置。 此演示应用程序的组件是:

  • App.js :这是根组件。
  • LoginButton.js :处理登录功能。
  • LogoutButon.js :处理注销功能。
  • Navbar.js :这包含注销和登录按钮。
  • Profile.js :这将保存登录用户的信息。

首先,我们在我们的 React 应用程序中安装 Auth0 的 React SDK。

 npm install @auth0/auth0-react

与我们使用 JavaScript SDK 进行设置的方式类似,我们设置了所需的 Auth0 凭据。 我们创建一个.env来存储您的应用程序的domaincleintId凭据。

 import {Auth0Provider} from '@auth0/auth0-react'; const domain = process.env.REACT_APP_AUTH0_DOMAIN const clientId = process.env.REACT_APP_AUTH0_CLIENT_ID ReactDOM.render( <Auth0Provider domain={domain} clientId={clientId} redirectUri={window.location.origin} > <App /> </Auth0Provider>, document.getElementById('root') );

要使用 SDK,我们需要将我们的应用程序包装在Auth0Provider组件中。 这将为您的应用程序内部的组件提供 React 上下文。 我们还设置了redirectUri ,这是 Auth0 在用户登录时将用户重定向到的地方。在后台,Auth0 React SDK 使用 React Context 来管理用户的身份验证状态。

设置登录

在这里,我们设置了登录按钮。

 import {useAuth0} from '@auth0/auth0-react'; import {Button} from './Styles'; const LoginButton = () => { const {loginWithPopup} = useAuth0() return( <Button onClick={() => loginWithPopup()}> Log in </Button> ) }

Auth0 为我们提供了两种在应用程序中设置登录的方式。 我们可以使用loginWithPopup()loginWithRedirect()方法。 在这种情况下,我使用loginWithPopup()

我们从 SDK 提供的useAuth0挂钩中解构loginWithPopup() 。 然后我们将loginWithPopup()传递给按钮的onClick事件。 这样,我们就设置了登录按钮。 如果我们使用loginWithRedirect() ,用户将被重定向到 Auth0 登录页面。 用户通过身份验证后,Auth0 会将其重定向回您的应用。

设置注销

让我们设置注销功能。

 import {Button} from './Styles'; import {useAuth0} from '@auth0/auth0-react'; const LogoutButton = () => { const {logout} = useAuth0() return( <Button onClick={() => logout()}> Log Out </Button> ) }

我们这里的内容类似于登录按钮设置。 唯一不同的是,我们从 SDK 中取出的是logout功能,也就是我们传递给按钮的onClick事件的功能。

调用logout()会将您的用户重定向到您的 Auth0 注销端点 ( https://YOUR_DOMAIN/v2/logout ),然后立即将他们重定向到您在应用设置的Allowed Logout URLs字段中指定的 URL。

跟踪身份验证状态

我们希望根据身份验证状态有条件地呈现LogoutButtonLoginButton

 import {StyledNavbar} from './Styles'; import {useAuth0} from '@auth0/auth0-react'; import LoginButton from './LoginButton'; import LogoutButton from './LogoutButton'; const Navbar = () => { const {isAuthenticated} = useAuth0() return ( <StyledNavbar> { isAuthenticated ? <LogoutButton/> : <LoginButton/> } </StyledNavbar> ) }

我们从useAuth0获得isAuthenticatedisAuthenticated是一个布尔值,它告诉我们是否有人已登录。 在我们的Navbar中,我们使用isAuthenticated有条件地呈现按钮。 我们不必像使用 JavaScript SDK 那样仅仅为了跟踪身份验证状态而设置多个自定义方法的繁琐过程。 isAuthenticated布尔值使我们的生活更轻松。

显示用户数据

一旦用户成功登录到我们的应用程序,我们希望显示用户的数据。

 import {useAuth0} from '@auth0/auth0-react' import {ProfileBox, Image, P} from './Styles'; const Profile = () => { const {user, isAuthenticated} = useAuth0() return( isAuthenticated && (<ProfileBox> <Image src={user.picture} alt={user.name}/> <P>Name: {user.name}</P> <P>Username: {user.nickname}</P> <P>Email: {user.email}</P> </ProfileBox>) ) }

登录后,我们可以访问user对象,我们可以从useAuth0获取该对象,并可以从该对象访问有关用户的信息。 在这里,我们还从useAuth0获取isAuthenticated ,因为我们只想在用户登录时显示数据。

与我们必须使用getAccessToken()getProfile()方法来访问用户配置文件的 JavaScript SDK 不同,我们不必使用 React SDK 这样做。

添加社交登录

默认情况下,Auth0 带有激活的 Google 登录。 但是,您可能希望为用户提供更多选项来登录您的应用程序。 让我们将 Github Login 添加到我们的应用程序中。

  • 在您的仪表板上,转到“连接”选项卡并选择“社交”。 在那里,您将看到已设置的连接。 单击创建连接按钮。 我已经在我的应用程序中启用了 Github,这就是你在这里看到它的原因。
社交连接设置
社交连接设置。 (大预览)
  • 选择 Github 连接。 我们将从 Github 获取clientIDclientSecret并将其放入社交连接设置中。
选择连接
选择连接。 (大预览)
Github 连接凭据
Github 连接凭据。 (大预览)
  • 接下来,您必须在 Github 上注册一个新应用程序。
注册一个新的 0Auth 应用
注册一个新的 0Auth 应用程序。 (大预览)

对于主页 URL 和授权回调 URL 字段,您可以使用https://localhost:3000或项目需要的任何 URL。

接下来,将客户端 ID 和 Secret 传递到您的 Auth0 帐户中的 Github 连接中。 有了这个,你已经设置了 Github 登录到你的应用程序。

结论

在本文中,我们了解了如何使用 Auth0 对我们的 React 应用程序进行身份验证。 我们还完成了在我们的应用程序中设置 Github 社交登录的过程。 使用 Auth0 为您的 React 应用程序添加身份验证很有趣。

我们还看到了如何使用 Auth0 对我们的应用程序进行身份验证,以及使用 React SDK 优于 JavaScript SDK 的开发人员体验优势。

资源

  • Auth0 文档
  • OpenID 连接范围
  • OpenID 连接协议
  • 代币
  • JSON 网络令牌
  • 访问令牌生命周期
  • 范围
  • JavaScript SDK
  • 反应 SDK