使用 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