This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.
使用 OAuth 或 OpenID 提供商进行身份验证
编辑页面
了解如何利用 expo-auth-session 库来实现与 OAuth 或 OpenID 提供商的身份验证。
expo-auth-session 提供了一个统一的 API,用于在 Android、iOS 和 web 上实现 OAuth 和 OpenID Connect 提供方。本指南将通过几个示例展示如何使用 AuthSession API。
所有身份验证提供方的规则
使用 AuthSession API 时,以下规则适用于所有身份验证提供方:
- 使用
WebBrowser.maybeCompleteAuthSession()来关闭 Web 弹出窗口。如果忘记添加这一行,弹出窗口将不会关闭。 - 使用
AuthSession.makeRedirectUri()创建重定向 URI,这会处理跨平台支持中的许多繁琐工作。在底层,它使用了expo-linking。 - 使用
AuthSession.useAuthRequest()构建请求,该 hook 支持异步初始化,这意味着移动浏览器不会阻塞身份验证。 - 在
request定义之前,请确保禁用提示。 - 在 Web 上只能在用户交互中调用
promptAsync。 - 由于无法自定义应用 scheme,Expo Go 不能用于 OAuth 或启用 OpenID Connect 的应用的本地开发和测试。你可以改用 Development Build,它提供类似 Expo Go 的开发体验,并支持登录后通过 OAuth 重定向回你的应用,其工作方式与生产环境完全一致。
获取访问令牌
大多数提供方使用 OAuth 2 标准进行安全身份验证和授权。在授权码授权流程中,身份提供方会返回一个一次性代码。然后使用该代码换取用户的访问令牌。
由于你的客户端应用代码不是安全存储密钥的地方,因此有必要在服务器中交换授权码,例如通过 API 路由 或 React Server Components。这将允许你安全地存储和使用客户端密钥,以访问提供方的令牌端点。
示例
以下示例展示了如何使用 AuthSession API 与几个常见提供方进行身份验证。
- 提供方每个应用只允许一个重定向 URI。你需要为每种想使用的方法分别创建一个独立的应用:
- 独立版 / 开发构建:
com.your.app://* - Web:
https://yourwebsite.com/*
- 独立版 / 开发构建:
redirectUri需要两个斜杠(://)。revocationEndpoint是动态的,并且需要你的config.clientId。
重定向 URI 模式
以下是一些你可能会使用的重定向 URI 模式示例。
独立版/开发版构建
yourscheme://path
在某些情况下,可能会有 1 到 3 个斜杠(/)。
-
环境:
- 现有的 React Native 应用
npx expo prebuild
- App Store 或 Play Store 中的独立版构建,或本地测试
- Android:
eas build或npx expo run:android - iOS:
eas build或npx expo run:ios
- Android:
- 现有的 React Native 应用
-
创建: 在正确的环境中运行时,使用
AuthSession.makeRedirectUri({ native: '<YOUR_URI>' })来选择原生 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 scheme。 - 如果更改了
expo.scheme,请运行npx expo prebuild --clean,使用新的 scheme 重新生成原生项目,然后使用npx expo run:android和npx expo run:ios重新构建。
-
使用:
promptAsync({ redirectUri })
改善用户体验
“登录流程”是整个过程中的重要环节,在许多情况下,用户会在此再次_下定决心_使用你的应用。糟糕的体验可能会导致用户甚至还没真正开始使用应用,就选择放弃。
以下是一些可以让用户身份验证更快速、更简单且更安全的建议:
预热浏览器
在 Android 上,你可以选择在使用浏览器之前对其进行预热。这样可以让浏览器应用在后台预先完成初始化。这样做可以显著加快向用户显示身份验证提示的速度。
import { useEffect } from 'react'; import * as WebBrowser from 'expo-web-browser'; function App() { useEffect(() => { WebBrowser.warmUpAsync(); return () => { WebBrowser.coolDownAsync(); }; }, []); // 执行身份验证 ... }
隐式登录
由于没有安全的方式将客户端密钥存储在你的应用包中,历史上许多提供商都提供了“隐式流程”,使你无需客户端密钥即可请求访问令牌。**由于存在固有的安全风险,包括访问令牌注入的风险,这种方式已不再推荐。**取而代之的是,大多数提供商现在都支持带有 PKCE(Proof Key for Code Exchange)扩展的授权码流程,以便在你的客户端应用代码中安全地将授权码交换为访问令牌。了解更多关于从隐式流程迁移到带有 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(); // 端点 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="登录" />; }
存储数据
在 Android 和 iOS 等原生平台上,你可以使用名为 expo-secure-store 的库在本地安全地存储访问令牌等数据(这与不安全的 AsyncStorage 不同)。它提供了对 Android 上加密的 SharedPreferences 和 iOS 上 keychain services 的原生访问。Web 上没有等效的功能。
你可以存储你的身份验证结果,并在之后重新恢复它们,从而避免再次提示用户登录。
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') { // 将认证安全地存储在你的设备上 SecureStore.setItemAsync(MY_SECURE_AUTH_STATE_KEY, storageValue); } } }, [response]); // 更多登录代码... }

