使用 OAuth 或 OpenID 提供商进行身份验证

了解如何利用 expo-auth-session 库通过 OAuth 或 OpenID 提供商实现身份验证。


expo-auth-session 提供了一个统一的 API,用于在 Android、iOS 和网页上实现 OAuth 和 OpenID Connect 提供者。本指南将通过几个示例向你展示如何使用 AuthSession API。

所有身份验证提供程序的规则

🌐 Rules for all authentication providers

在使用 AuthSession API 时,以下规则适用于所有身份验证提供程序:

🌐 When using the AuthSession API, the following rules apply to all authentication providers:

  • 使用 WebBrowser.maybeCompleteAuthSession() 来关闭网页弹出窗口。如果忘记添加此操作,弹出窗口将无法关闭。
  • 使用 AuthSession.makeRedirectUri() 创建重定向,这处理了与通用平台支持相关的大部分繁重工作。在后台,它使用 expo-linking
  • 使用 AuthSession.useAuthRequest() 构建请求,该钩子允许异步设置,这意味着移动浏览器不会阻塞身份验证。
  • 确保在定义 request 之前禁用提示。
  • 你只能在网页上的用户交互中调用 promptAsync
  • 由于无法自定义应用方案,Expo Go 不能用于启用 OAuth 或 OpenID Connect 的应用的本地开发和测试。你可以改用 开发构建,它提供类似 Expo Go 的开发体验,并支持在登录后将 OAuth 重定向回应用,就像在生产环境中一样。

获取访问令牌

🌐 Obtaining access tokens

大多数提供商使用 OAuth 2 标准进行安全认证和授权。在授权码授权中,身份提供者会返回一个一次性代码。然后,该代码会被用于交换用户的访问令牌。

🌐 Most providers use the OAuth 2 standard for secure authentication and authorization. In the authorization code grant, the identity provider returns a one-time code. This code is then exchanged for the user's access token.

由于你的客户端应用代码不是存储密钥信息的安全位置,因此有必要在服务器中交换授权码,例如使用API 路由React 服务器组件。这将允许你安全地存储并使用客户端密钥来访问提供者的令牌端点。

🌐 Since your client application code is not a secure place to store secrets, it is necessary to exchange the authorization code in a server such as with API routes or React Server Components. This will allow you to securely store and use a client secret to access the provider's token endpoint.

示例

🌐 Examples

以下示例展示了如何使用 AuthSession API 来与一些流行的提供商进行身份验证。

🌐 The following examples show how to use the AuthSession API to authenticate with a few popular providers.

网站提供者PKCE自动发现
获取你的配置OAuth 2.0支持不可用
  • 提供者每个应用只允许一个重定向 URI。你需要为你想使用的每种方法创建单独的应用:
    • 独立 / 开发构建:com.your.app://*
    • 网站:https://yourwebsite.com/*
  • redirectUri 需要两个斜杠(://)。
  • revocationEndpoint 是动态的,需要你的 config.clientId
GitHub Auth Example
import { useEffect } from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest } from 'expo-auth-session'; import { Button } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://github.com/login/oauth/authorize', tokenEndpoint: 'https://github.com/login/oauth/access_token', revocationEndpoint: 'https://github.com/settings/connections/applications/<CLIENT_ID>', }; export default function App() { const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['identity'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="登录" onPress={() => { promptAsync(); }} /> ); }
网站提供商PKCE自动发现
注册 > 应用OpenID支持可用
  • 你无法定义自定义的 redirectUri,Okta 将为你提供一个。
Okta Auth Example
import { useEffect } from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session'; import { Button, Platform } from 'react-native'; WebBrowser.maybeCompleteAuthSession(); export default function App() { // Endpoint const discovery = useAutoDiscovery('https://<OKTA_DOMAIN>.com/oauth2/default'); // Request const [request, response, promptAsync] = useAuthRequest( { clientId: 'CLIENT_ID', scopes: ['openid', 'profile'], redirectUri: makeRedirectUri({ native: 'com.okta.<OKTA_DOMAIN>:/callback', }), }, discovery ); useEffect(() => { if (response?.type === 'success') { const { code } = response.params; } }, [response]); return ( <Button disabled={!request} title="登录" onPress={() => { promptAsync(); }} /> ); }

重定向 URI 模式

🌐 Redirect URI patterns

以下是你可能最终使用的一些常见重定向 URI 模式的示例。

🌐 Here are a few examples of some common redirect URI patterns you may end up using.

独立/开发构建

🌐 Standalone/development build

yourscheme://path

在某些情况下,会有 1 到 3 个斜杠(/)。

🌐 In some cases there will be anywhere between 1 to 3 slashes (/).

  • 环境:
    • 裸工作流程
      • npx expo prebuild
    • 在 App 或 Play Store 中独立构建或在本地测试
      • 安卓:eas buildnpx expo run:android
      • iOS:eas buildnpx expo run:ios
  • 创建: 在正确的环境中运行时,使用 AuthSession.makeRedirectUri({ native: '<YOUR_URI>' }) 选择本地模式。
    • your.app://redirect -> makeRedirectUri({ scheme: 'your.app', path: 'redirect' })
    • your.app:/// -> makeRedirectUri({ scheme: 'your.app', isTripleSlashed: true })
    • your.app:/authorize -> makeRedirectUri({ native: 'your.app:/authorize' })
    • your.app://auth?foo=bar -> makeRedirectUri({ scheme: 'your.app', path: 'auth', queryParams: { foo: 'bar' } })
    • exp://u.expo.dev/[project-id]?channel-name=[channel-name]&runtime-version=[runtime-version] -> makeRedirectUri()
    • 这个链接通常可以自动创建,但我们建议你至少定义 scheme 属性。通过传递 native 属性,可以在应用中覆盖整个 URL。通常,这将用于像 Google 或 Okta 这样的提供商,它们要求你使用自定义的原生 URI 重定向。你可以使用 npx uri-scheme 来添加、列出和打开 URI 方案。
    • 如果在弹出 expo.scheme 之后进行更改,那么你需要使用 expo apply 命令将更改应用到你的本地项目,然后重新构建它们(yarn iosyarn android)。
  • 用法: promptAsync({ redirectUri })

改善用户体验

🌐 Improving user experience

“登录流程”是一个非常重要的环节,在很多情况下,这正是用户决定是否再次使用你应用的关键时刻。糟糕的体验可能会导致用户在真正使用你的应用之前就放弃它。

🌐 The "login flow" is an important thing to get right, in a lot of cases this is where the user will commit to using your app again. A bad experience can cause users to give up on your app before they've really gotten to use it.

以下是一些技巧,可帮助你快速、轻松、安全地进行用户身份验证:

🌐 Here are a few tips you can use to make authentication quick, easy, and secure for your users:

预热浏览器

🌐 Warming the browser

在 Android 上,你可以选择在使用网页浏览器之前先进行预热。这允许浏览器应用在后台预先初始化自己。这样做可以显著加快提示用户进行身份验证的速度。

🌐 On Android you can optionally warm up the web browser before it's used. This allows the browser app to pre-initialize itself in the background. Doing this can significantly speed up prompting the user for authentication.

import { useEffect } from 'react'; import * as WebBrowser from 'expo-web-browser'; function App() { useEffect(() => { WebBrowser.warmUpAsync(); return () => { WebBrowser.coolDownAsync(); }; }, []); // Do authentication ... }

隐式登录

🌐 Implicit login

由于没有安全的方法将客户端密钥存储在应用包中,历史上许多提供商提供了“隐式流”,它使你可以在不使用客户端密钥的情况下请求访问令牌。由于固有的安全风险,包括访问令牌注入风险,这种方式已不再推荐。相反,大多数提供商现在支持带 PKCE(代码交换验证密钥)的授权代码扩展,以便在客户端应用代码中安全地将授权代码交换为访问令牌。了解有关从隐式流过渡到带 PKCE 的授权代码的更多信息。

🌐 Because there was no secure way to do this to store client secrets in your app bundle, historically, many providers have offered an "Implicit flow" which enables you to request an access token without the client secret. This is no longer recommended due to inherent security risks, including the risk of access token injection. Instead, most providers now support the authorization code with PKCE (Proof Key for Code Exchange) extension to securely exchange an authorization code for an access token within your client app code. Learn more about transitioning from Implicit flow to authorization code with PKCE.

expo-auth-session 仍然支持为了旧代码的兼容性而使用隐式流程。下面是隐式流程的一个示例实现。

import { useEffect } from 'react'; import * as WebBrowser from 'expo-web-browser'; import { makeRedirectUri, useAuthRequest, ResponseType } from 'expo-auth-session'; WebBrowser.maybeCompleteAuthSession(); // Endpoint const discovery = { authorizationEndpoint: 'https://accounts.spotify.com/authorize', }; function App() { const [request, response, promptAsync] = useAuthRequest( { responseType: ResponseType.Token, clientId: 'CLIENT_ID', scopes: ['user-read-email', 'playlist-modify-public'], redirectUri: makeRedirectUri({ scheme: 'your.app' }), }, discovery ); useEffect(() => { if (response && response.type === 'success') { const token = response.params.access_token; } }, [response]); return <Button disabled={!request} onPress={() => promptAsync()} title="Login" />; }

存储数据

🌐 Storing data

在 Android 和 iOS 等原生平台上,你可以使用名为 expo-secure-store 的库在本地安全地存储访问令牌等内容(这与不安全的 AsyncStorage 不同)。它提供了对 Android 上加密的 SharedPreferences 和 iOS 上 密钥串服务 的原生访问。网页端没有相应的功能。

🌐 On native platforms such as Android and iOS, you can secure things like access tokens locally using a library called expo-secure-store (This is different to AsyncStorage which is not secure). It provides native access to encrypted SharedPreferences on Android and keychain services on iOS and . There is no web equivalent to this functionality.

你可以存储身份验证结果并在以后重新水化它们,以避免提示用户再次登录。

🌐 You can store your authentication results and rehydrate them later to avoid having to prompt the user to login again.

import * as SecureStore from 'expo-secure-store'; const MY_SECURE_AUTH_STATE_KEY = 'MySecureAuthStateKey'; function App() { const [, response] = useAuthRequest({}); useEffect(() => { if (response && response.type === 'success') { const auth = response.params; const storageValue = JSON.stringify(auth); if (Platform.OS !== 'web') { // Securely store the auth on your device SecureStore.setItemAsync(MY_SECURE_AUTH_STATE_KEY, storageValue); } } }, [response]); // More login code... }