了解如何利用 expo-auth-session 库通过 OAuth 或 OpenID 提供商实现身份验证。
Expo 可用于登录 Android、iOS 和 Web 上的许多流行提供商。这些指南中的大多数都使用纯 JS AuthSession
API,请参阅这些文档以获取有关 API 的更多信息。
¥Expo can be used to login to many popular providers on Android, iOS, and web. Most of these guides utilize the pure JS AuthSession
API, refer to those docs for more information on the API.
以下是适用于所有身份验证提供商的一些重要规则:
¥Here are some important rules that apply to all authentication providers:
使用 WebBrowser.maybeCompleteAuthSession()
关闭 Web 弹出窗口。如果你忘记添加此项,则弹出窗口将不会关闭。
¥Use WebBrowser.maybeCompleteAuthSession()
to dismiss the web popup. If you forget to add this then the popup window will not close.
使用 AuthSession.makeRedirectUri()
创建重定向,这完成了与通用平台支持相关的大量繁重工作。在幕后,它使用 expo-linking
。
¥Create redirects with AuthSession.makeRedirectUri()
this does a lot of the heavy lifting involved with universal platform support. Behind the scenes, it uses expo-linking
.
使用 AuthSession.useAuthRequest()
构建请求,该钩子允许异步设置,这意味着移动浏览器不会阻止身份验证。
¥Build requests using AuthSession.useAuthRequest()
, the hook allows for async setup which means mobile browsers won't block the authentication.
确保在定义 request
之前禁用提示。
¥Be sure to disable the prompt until request
is defined.
你只能在 Web 上的用户交互中调用 promptAsync
。
¥You can only invoke promptAsync
in user interaction on the web.
由于无法自定义你的应用方案,Expo Go 无法用于本地开发和测试支持 OAuth 或 OpenID Connect 的应用。你可以改为使用 开发构建,它可以实现类似于 Expo Go 的开发体验,并支持登录后 OAuth 重定向回你的应用,其工作方式与生产中的工作方式相同。
¥Expo Go cannot be used for local development and testing of OAuth or OpenID Connect-enabled apps due to the inability to customize your app scheme. You can instead use a Development Build, which enables an Expo Go-like development experience and supports OAuth redirects back to your app after login in a manner that works just like it would in production.
¥Obtaining access tokens
本指南中的大多数提供都使用 OAuth 2 标准进行安全身份验证和授权。在授权代码授予中,身份提供者返回一次性代码。然后将此代码交换为用户的访问令牌。
¥Most provides in this guide 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.
由于 你的应用代码不是存储密钥的安全场所,需要在上下文(例如,你的服务器)中交换授权代码。这将允许你安全地存储和使用客户端密钥来访问提供商的令牌端点。
¥Since your application code is not a secure place to store secrets, it is necessary to exchange the authorization code in a context (for example, your server). This will allow you to securely store and use a client secret to access the provider's token endpoint.
¥Guides
AuthSession 可用于任何 OAuth 或 OpenID Connect 提供商,我们已经整理了使用最需要的服务的指南!如果你想查看更多内容,可以选择 开启 PR 或 投票给精明的人。
¥AuthSession can be used for any OAuth or OpenID Connect provider, we've assembled guides for using the most requested services! If you'd like to see more, you can open a PR or vote on canny.
OAuth 2 | OpenID
OAuth 2 | OpenID
OAuth 2 | OpenID
iOS Only
OAuth 2 | OpenID
OAuth 2
OAuth 2 | OpenID
OAuth 2
OAuth 2 | OpenID
OAuth 2
OAuth 2
OAuth 2
Recaptcha
OAuth 2
OAuth 2 | OpenID
OAuth 2
OAuth 2 | OpenID
OAuth 2 | OpenID
OAuth 2 | OpenID
OAuth 2
OAuth 2
OAuth 2
OAuth 2
OAuth 2
OAuth 2
OAuth 2
OAuth 2
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
更多信息 | OpenID | 必需的 | 可用的 |
如果不包含 offline_access
,则不会返回刷新令牌。
¥If offline_access
isn't included then no refresh token will be returned.
import * as React from 'react';
import { Button, Text, View } from 'react-native';
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';
WebBrowser.maybeCompleteAuthSession();
const redirectUri = AuthSession.makeRedirectUri();
export default function App() {
const discovery = AuthSession.useAutoDiscovery('https://demo.identityserver.io');
// Create and load an auth request
const [request, result, promptAsync] = AuthSession.useAuthRequest(
{
clientId: 'native.code',
redirectUri,
scopes: ['openid', 'profile', 'email', 'offline_access'],
},
discovery
);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button title="Login!" disabled={!request} onPress={() => promptAsync()} />
{result && <Text>{JSON.stringify(result, null, 2)}</Text>}
</View>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OpenID | 支持的 | 可用的 |
确保选中控制台中的 Public Client
选项。
¥Make sure to check Public Client
option in the console.
在允许的授权类型部分中选择预期的授权。
¥Choose the intended grant in the Allowed grant types section.
import { useState, useEffect } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import * as AuthSession from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import jwtDecode from "jwt-decode";
WebBrowser.maybeCompleteAuthSession();
const redirectUri = AuthSession.makeRedirectUri();
const CLIENT_ID = "YOUR_CLIENT_ID";
export default function App() {
const discovery = AuthSession.useAutoDiscovery('https://api.asgardeo.io/t/<YOUR_ORG_NAME>/oauth2/token');
const [tokenResponse, setTokenResponse] = useState({});
const [decodedIdToken, setDecodedIdToken] = useState({});
const [request, result, promptAsync] = AuthSession.useAuthRequest(
{
redirectUri,
clientId: CLIENT_ID,
responseType: "code",
scopes: ["openid", "profile", "email"]
},
discovery
);
const getAccessToken = () => {
if (result?.params?.code) {
fetch(
"https://api.asgardeo.io/t/iamapptesting/oauth2/token",
{
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: `grant_type=authorization_code&code=${result?.params?.code}&redirect_uri=${redirectUri}&client_id=${CLIENT_ID}&code_verifier=${request?.codeVerifier}`
}).then((response) => {
return response.json();
}).then((data) => {
setTokenResponse(data);
setDecodedIdToken(jwtDecode(data.id_token));
}).catch((err) => {
console.log(err);
});
}
}
useEffect(() => {
(async function setResult() {
if (result) {
if (result.error) {
Alert.alert(
"Authentication error",
result.params.error_description || "something went wrong"
);
return;
}
if (result.type === "success") {
getAccessToken();
}
}
})();
}, [result]);
return (
<View style={styles.container}>
<Button title="Login" disabled={!request} onPress={() => promptAsync()} />
{decodedIdToken && <Text>Welcome {decodedIdToken.given_name || ""}!</Text>}
{decodedIdToken && <Text>{decodedIdToken.email}</Text>}
<View style={styles.accessTokenBlock}>
decodedToken && <Text>Access Token: {tokenResponse.access_token}</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
accessTokenBlock: {
width: 300,
height: 500,
overflow: "scroll"
}
});
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OpenID | 支持的 | 可用的 |
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import {
exchangeCodeAsync,
makeRedirectUri,
useAuthRequest,
useAutoDiscovery,
} from 'expo-auth-session';
import { Button, Text, SafeAreaView } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
export default function App() {
// Endpoint
const discovery = useAutoDiscovery(
'https://login.microsoftonline.com/<TENANT_ID>/v2.0',
);
const redirectUri = makeRedirectUri({
scheme: undefined,
path: 'auth',
});
const clientId = '<CLIENT_ID>';
// We store the JWT in here
const [token, setToken] = React.useState<string | null>(null);
// Request
const [request, , promptAsync] = useAuthRequest(
{
clientId,
scopes: ['openid', 'profile', 'email', 'offline_access'],
redirectUri,
},
discovery,
);
return (
<SafeAreaView>
<Button
disabled=
{!request}
title="Login"
onPress={() => {
promptAsync().then((codeResponse) => {
if (request && codeResponse?.type === 'success' && discovery) {
exchangeCodeAsync(
{
clientId,
code: codeResponse.params.code,
extraParams: request.codeVerifier
? { code_verifier: request.codeVerifier }
: undefined,
redirectUri,
},
discovery,
).then((res) => {
setToken(res.accessToken);
});
}
});
}}
/>
<Text>{token}</Text>
</SafeAreaView>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OpenID | 支持的 | 可用的 |
Beyond Identity 允许开发者基于称为通用密钥的公私密钥对实现强大的无密码身份验证。所有密钥均以加密方式链接到用户,并且可以使用 Beyond Identity API 进行集中管理。
¥Beyond Identity allows developers to implement strong passwordless authentication based on public-private key pairs called Universal Passkeys. All keys are cryptographically linked to the user and can be centrally managed using the Beyond Identity APIs.
在进行身份验证之前,你将需要通用密钥。参见 超越身份证明文件。
¥You will need a Universal Passkey before you can authenticate. See Beyond Identity documentation.
¥Make sure to create a development build and follow instructions to install required config plugins.
有关完整的示例应用,请参阅 SDK 的 GitHub 存储库。
¥For a complete example app, see SDK's GitHub repository.
将 Beyond Identity 验证器配置 调用类型设置为自动。
¥Set your Beyond Identity Authenticator Config's Invocation Type to Automatic.
如果选择“自动”,Beyond Identity 将使用调用 URL(指向你的应用的 App Scheme 或通用 URL)自动重定向到你的应用。
¥If Automatic is selected, Beyond Identity will automatically redirect to your application using the Invoke URL (the App Scheme or Universal URL pointing to your application).
import { useEffect } from 'react';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Button } from 'react-native';
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
export default function App() {
// Endpoint
const discovery = useAutoDiscovery(
`https://auth-${region}.beyondidentity.com/v1/tenants/${tenant_id}/realms/${realm_id}/applications/${application_id}`
);
// Request
const [request, response, promptAsync] = useAuthRequest(
{
clientId: `${client_id}`,
scopes: ['openid'],
redirectUri: makeRedirectUri({
scheme: 'your.app',
}),
},
discovery
);
useEffect(() => {
const authenticate = async url => {
// Display UI for the user to select a passwordless passkey
const passkeys = await Embedded.getPasskeys();
if (await Embedded.isAuthenticateUrl(url)) {
// Pass url and a selected passkey ID into the Beyond Identity Embedded SDK authenticate function
const { redirectUrl } = await Embedded.authenticate(url, passkeys[0].id);
}
};
if (response?.url) {
authenticate(url);
}
}, [response]);
return (
<Button
disabled={!request}
title="Passwordless Login"
onPress={() => {
promptAsync();
}}
/>
);
}
将 Beyond Identity 验证器配置 调用类型设置为手动。
¥Set your Beyond Identity Authenticator Config's Invocation Type to Manual.
如果选择“手动”,身份验证 URL 将作为 JSON 响应的一部分返回。不需要重定向,也不需要 Web 服务身份验证。结果是使用密钥进行完全静默的 OAuth 2.0 身份验证。
¥If Manual is selected, an authentication URL is returned as part of a JSON response. No redirects are needed and do not require web service authentication. The result is a completely silent OAuth 2.0 authentication using Passkeys.
import React from 'react';
import { Button } from 'react-native';
import { Embedded } from '@beyondidentity/bi-sdk-react-native';
export default function App() {
async function authenticate() {
const BeyondIdentityAuthUrl = `https://auth-${region}.beyondidentity.com/v1/tenants/${tenant_od}/realms/${realm_id}/applications/${application_id}/authorize?response_type=code&client_id=${client_id}&redirect_uri=${uri_encoded_redirect_uri}&scope=openid&state=${state}&code_challenge_method=S256&code_challenge=${pkce_code_challenge}`;
let response = await fetch(BeyondIdentityAuthUrl, {
method: 'GET',
headers: new Headers({
'Content-Type': 'application/json',
}),
});
const data = await response.json();
// Display UI for the user to select a passwordless passkey
const passkeys = await Embedded.getPasskeys();
if (await Embedded.isAuthenticateUrl(data.authenticate_url)) {
// Pass url and selected Passkey ID into the Beyond Identity Embedded SDK authenticate function
const { redirectUrl } = await Embedded.authenticate(data.authenticate_url, passkeys[0].id);
}
}
return (
<Button
title="Passwordless Login"
onPress={authenticate}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
redirectUri
需要两个斜线(://
)。
¥redirectUri
requires two slashes (://
).
示例重定向 Uri:
¥Example redirectUri:
独立/开发构建:myapp://*
¥Standalone / development build: myapp://*
网址:https://yourwebsite.com/*
¥Web: https://yourwebsite.com/*
import * as WebBrowser from 'expo-web-browser';
import {
makeRedirectUri,
useAuthRequest,
exchangeCodeAsync,
} from "expo-auth-session";
import React, { useEffect, useState } from "react";
WebBrowser.maybeCompleteAuthSession();
const discovery = {
authorizationEndpoint: "https://auth.calendly.com/oauth/authorize",
tokenEndpoint: "https://auth.calendly.com/oauth/token",
};
export default function App() {
const [authTokens, setAuthTokens] = useState({access_token: "", refresh_token: ""});
const [request, response, promptAsync] = useAuthRequest(
{
clientId: process.env.EXPO_PUBLIC_Client_ID,
usePKCE: true,
redirectUri: makeRedirectUri({
native: "myapp://",
}),
},
discovery
);
useEffect(() => {
const exchange = async (exchangeTokenReq) => {
try {
const exchangeTokenResponse = await exchangeCodeAsync(
{
clientId: process.env.EXPO_PUBLIC_Client_ID,
code: exchangeTokenReq,
redirectUri: makeRedirectUri({
native: "myapp://",
}),
extraParams: {
code_verifier: request.codeVerifier,
},
},
discovery
);
setAuthTokens(exchangeTokenResponse);
} catch (error) {
console.error("error", error);
}
};
if (response) {
if (response.error) {
console.error(
"Authentication error",
response.params.error_description || "something went wrong"
);
}
if (response.type === "success") {
exchange( response.params.code);
}
}
}, [discovery, request, response]);
return (
<SafeAreaView>
<View>
<Text>0Auth2</Text>
<Button
title="Connect to Calendly"
onPress={() => {
promptAsync();
}}
/>
<Text>AuthTokens: {JSON.stringify(authTokens)}</Text>
</View>
</SafeAreaView>
)
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OpenID | 支持的 | 无法使用 |
利用 Cognito 中的托管 UI (API 文档)
¥Leverages the Hosted UI in Cognito (API documentation)
成功验证后请求代码,然后用代码交换身份验证令牌 (PKCE)
¥Requests code after successfully authenticating, followed by exchanging code for the auth tokens (PKCE)
/token
端点需要一个 code_verifier
参数,你可以在调用 exchangeCodeAsync()
之前从请求中检索该参数:
¥The /token
endpoint requires a code_verifier
parameter which you can retrieve from the request before calling exchangeCodeAsync()
:
extraParams: {
code_verifier: request.codeVerifier,
}
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { useAuthRequest, exchangeCodeAsync, revokeAsync, ResponseType } from 'expo-auth-session';
import { Button, Alert } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
const clientId = '<your-client-id-here>';
const userPoolUrl =
'https://<your-user-pool-domain>.auth.<your-region>.amazoncognito.com';
const redirectUri = 'your-redirect-uri';
export default function App() {
const [authTokens, setAuthTokens] = React.useState(null);
const discoveryDocument = React.useMemo(() => ({
authorizationEndpoint: userPoolUrl + '/oauth2/authorize',
tokenEndpoint: userPoolUrl + '/oauth2/token',
revocationEndpoint: userPoolUrl + '/oauth2/revoke',
}), []);
const [request, response, promptAsync] = useAuthRequest(
{
clientId,
responseType: ResponseType.Code,
redirectUri,
usePKCE: true,
},
discoveryDocument
);
React.useEffect(() => {
const exchangeFn = async (exchangeTokenReq) => {
try {
const exchangeTokenResponse = await exchangeCodeAsync(
exchangeTokenReq,
discoveryDocument
);
setAuthTokens(exchangeTokenResponse);
} catch (error) {
console.error(error);
}
};
if (response) {
if (response.error) {
Alert.alert(
'Authentication error',
response.params.error_description || 'something went wrong'
);
return;
}
if (response.type === 'success') {
exchangeFn({
clientId,
code: response.params.code,
redirectUri,
extraParams: {
code_verifier: request.codeVerifier,
},
});
}
}
}, [discoveryDocument, request, response]);
const logout = async () => {
const revokeResponse = await revokeAsync(
{
clientId: clientId,
token: authTokens.refreshToken,
},
discoveryDocument
);
if (revokeResponse) {
setAuthTokens(null);
}
};
console.log('authTokens: ' + JSON.stringify(authTokens));
return authTokens ? (
<Button title="Logout" onPress={() => logout()} />
) : (
<Button disabled={!request} title="Login" onPress={() => promptAsync()} />
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
redirectUri
需要两个斜杠(://
)。
¥The redirectUri
requires two slashes (://
).
范围必须与 :
连接,因此只需创建一个长字符串。
¥Scopes must be joined with :
so just create one long string.
设置重定向 URI:你的项目 > 允许的重定向 URI:(修改后一定要保存)。
¥Setup redirect URIs: Your Project > Permitted Redirect URIs: (be sure to save after making changes).
网络开发:https://localhost:19006
¥Web dev: https://localhost:19006
运行 expo start --web --https
以使用 https 运行,否则 auth 将无法工作。
¥Run expo start --web --https
to run with https, auth won't work otherwise.
在 URL 末尾添加斜杠并不重要。
¥Adding a slash to the end of the URL doesn't matter.
独立/开发构建:your-scheme://
¥Standalone / development build: your-scheme://
方案应在 app.json expo.scheme: 'your-scheme'
中指定,然后使用 makeRedirectUri({ native: 'your-scheme://' })
添加到应用代码中)
¥Scheme should be specified in app.json expo.scheme: 'your-scheme'
, then added to the app code with makeRedirectUri({ native: 'your-scheme://' })
)
网页制作:https://yourwebsite.com
¥Web production: https://yourwebsite.com
将其设置为你部署的网站 URL 的值。
¥Set this to whatever your deployed website URL is.
import {
exchangeCodeAsync,
makeRedirectUri,
TokenResponse,
useAuthRequest,
} from "expo-auth-session";
import * as WebBrowser from "expo-web-browser";
import * as React from "react";
import { Button } from "react-native";
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: "https://www.coinbase.com/oauth/authorize",
tokenEndpoint: "https://api.coinbase.com/oauth/token",
revocationEndpoint: "https://api.coinbase.com/oauth/revoke",
};
const redirectUri = makeRedirectUri({ scheme: 'your.app'});
const CLIENT_ID = "CLIENT_ID";
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: CLIENT_ID,
scopes: ["wallet:accounts:read"],
redirectUri,
},
discovery
);
const {
// The token will be auto exchanged after auth completes.
token,
exchangeError,
} = useAutoExchange(
response?.type === "success" ? response.params.code : null
);
React.useEffect(() => {
if (token) {
console.log("My Token:", token.accessToken);
}
}, [token]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
type State = {
token: TokenResponse | null;
exchangeError: Error | null;
};
// A hook to automatically exchange the auth token for an access token.
// this should be performed in a server and not here in the application.
// For educational purposes only:
function useAutoExchange(code?: string): State {
const [state, setState] = React.useReducer(
(state: State, action: Partial<State>) => ({ ...state, ...action }),
{ token: null, exchangeError: null }
);
const isMounted = useMounted();
React.useEffect(() => {
if (!code) {
setState({ token: null, exchangeError: null });
return;
}
exchangeCodeAsync(
{
clientId: CLIENT_ID,
clientSecret: "CLIENT_SECRET",
code,
redirectUri,
},
discovery
)
.then((token) => {
if (isMounted.current) {
setState({ token, exchangeError: null });
}
})
.catch((exchangeError) => {
if (isMounted.current) {
setState({ exchangeError, token: null });
}
});
}, [code]);
return state;
}
function useMounted() {
const isMounted = React.useRef(true);
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OpenID | 支持的 | 可用的 |
利用托管 Descope Flow 应用 身份验证托管应用。
¥Leverages the Hosted Descope Flow app Auth-Hosting App.
成功验证后请求代码,然后用代码交换身份验证令牌 (PKCE)。
¥Requests code after successfully authenticating, followed by exchanging code for the auth tokens (PKCE).
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import * as AuthSession from 'expo-auth-session';
import { Button, View } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
const descopeProjectId = '<Descope Project ID>'; // Replace this with your Descope Project ID
const descopeUrl = `https://api.descope.com/${descopeProjectId}`;
const redirectUri = AuthSession.makeRedirectUri();
export default function App() {
const [authTokens, setAuthTokens] = React.useState(null);
const discovery = AuthSession.useAutoDiscovery(descopeUrl);
const [request, response, promptAsync] = AuthSession.useAuthRequest(
{
clientId: descopeProjectId,
responseType: AuthSession.ResponseType.Code,
redirectUri,
usePKCE: true,
scopes: ['openid', 'profile', 'email'],
},
discovery
);
React.useEffect(() => {
if (response) {
if (response.error) {
console.error(
'Authentication error',
response.params.error_description || 'something went wrong'
);
return;
}
if (response.type === 'success') {
const exchangeFn = async (exchangeTokenReq) => {
try {
const exchangeTokenResponse = await AuthSession.exchangeCodeAsync(
exchangeTokenReq,
discovery
);
setAuthTokens(exchangeTokenResponse);
} catch (error) {
console.error(error);
}
};
exchangeFn({
clientId: descopeProjectId,
code: response.params.code,
redirectUri,
extraParams: {
code_verifier: request.codeVerifier,
},
});
}
}
}, [discovery, request, response]);
const logout = async () => {
const revokeResponse = await AuthSession.revokeAsync(
{
clientId: descopeProjectId,
token: authTokens.refreshToken,
},
discovery
);
if (revokeResponse) {
setAuthTokens(null);
}
};
return (
<View>
{authTokens ? (
<Button title="Logout" onPress={logout} />
) : (
<Button
disabled={!request}
title="Login"
onPress={promptAsync}
/>
)}
</View>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 不支持 | 无法使用 |
范围必须是空数组。
¥Scopes must be an empty array.
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: 'https://www.dropbox.com/oauth2/authorize',
tokenEndpoint: 'https://www.dropbox.com/oauth2/token',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
// There are no scopes so just pass an empty array
scopes: [],
redirectUri: makeRedirectUri({
scheme: 'your.app',
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
提供商仅允许每个应用使用一个重定向 URI。对于要使用的每种方法,你都需要一个单独的应用:
¥Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
独立/开发构建:com.your.app://*
¥Standalone / development build: com.your.app://*
网址:https://yourwebsite.com/*
¥Web: https://yourwebsite.com/*
redirectUri
需要两个斜杠(://
)。
¥The redirectUri
requires two slashes (://
).
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: 'https://www.fitbit.com/oauth2/authorize',
tokenEndpoint: 'https://api.fitbit.com/oauth2/token',
revocationEndpoint: 'https://api.fitbit.com/oauth2/revoke',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
scopes: ['activity', 'sleep'],
redirectUri: makeRedirectUri({
scheme: 'your.app'
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
提供商仅允许每个应用使用一个重定向 URI。对于要使用的每种方法,你都需要一个单独的应用:
¥Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
独立/开发构建:com.your.app://*
¥Standalone / development build: com.your.app://*
网址:https://yourwebsite.com/*
¥Web: https://yourwebsite.com/*
redirectUri
需要两个斜杠(://
)。
¥The redirectUri
requires two slashes (://
).
revocationEndpoint
是动态的,需要你的 config.clientId
。
¥revocationEndpoint
is dynamic and requires your config.clientId
.
import * as React 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
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
你将需要为每个平台创建不同的提供商应用(动态选择你的 clientId
)。
¥You will need to create a different provider app for each platform (dynamically choosing your clientId
).
在这里了解更多:imgur.com/oauth2
¥Learn more here: imgur.com/oauth2
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
const discovery = {
authorizationEndpoint: 'https://api.imgur.com/oauth2/authorize',
tokenEndpoint: 'https://api.imgur.com/oauth2/token',
};
export default function App() {
// Request
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
clientSecret: 'CLIENT_SECRET',
redirectUri: makeRedirectUri({
scheme: 'your.app',
}),
// imgur requires an empty array
scopes: [],
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
* | OpenID | 必需的 | 可用的 |
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { Button, Text, View } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
export default function App() {
const discovery = useAutoDiscovery('https://YOUR_KEYCLOAK/realms/YOUR_REALM');
// Create and load an auth request
const [request, result, promptAsync] = useAuthRequest(
{
clientId: 'YOUR_CLIENT_NAME',
redirectUri: makeRedirectUri({
scheme: 'YOUR_SCHEME'
}),
scopes: ['openid', 'profile'],
},
discovery
);
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button title="Login!" disabled={!request} onPress={() => promptAsync()} />
{result && <Text>{JSON.stringify(result, null, 2)}</Text>}
</View>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OpenID | 支持的 | 可用的 |
对于原生平台,redirectUri
需要私有使用的原生 URI 方案。详细信息请参见 OAuth2 规范。
¥For native platforms, a Private-Use native URI scheme is required for redirectUri
. See OAuth2 spec for more details.
对于 Web 平台,redirectUri
需要 http(s)://
方案。
¥For web platform, a http(s)://
scheme is required for redirectUri
.
import { LogtoProvider, useLogto } from "@logto/rn";
// Use `useLogto()` hook to sign in and sign out
const Content = () => {
const { signIn, signOut, isAuthenticated } = useLogto();
return isAuthenticated ? (
<Button title="Sign Out" onPress={signOut} />
) : (
<Button title="Sign In" onPress={async () => signIn(redirectUri)} />
);
};
// Wrap your page component with `LogtoProvider`
const App = () => {
const logtoConfig = {
appId: "YOUR_APP",
endpoint: "YOUR_LOGTO_ENDPOINT",
};
return (
<LogtoProvider config={logtoConfig}>
<Content />
</LogtoProvider>
);
};
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
报名 > 应用 | OpenID | 支持的 | 可用的 |
你无法定义自定义 redirectUri
,Okta 将为你提供一个。
¥You cannot define a custom redirectUri
, Okta will provide you with one.
import * as React 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
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
提供商仅允许每个应用使用一个重定向 URI。对于要使用的每种方法,你都需要一个单独的应用:
¥Provider only allows one redirect URI per app. You'll need an individual app for every method you want to use:
独立/开发构建:com.your.app://*
¥Standalone / development build: com.your.app://*
网址:https://yourwebsite.com/*
¥Web: https://yourwebsite.com/*
redirectUri
需要两个斜杠(://
)。
¥The redirectUri
requires two slashes (://
).
import * as React 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://www.reddit.com/api/v1/authorize.compact',
tokenEndpoint: 'https://www.reddit.com/api/v1/access_token',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
scopes: ['identity'],
redirectUri: makeRedirectUri({
native: 'your.app://redirect',
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
redirectUri
需要两个斜杠(://
)。
¥The redirectUri
requires two slashes (://
).
redirectUri
可以在网站的 "OAuth 和权限" 部分下定义。
¥redirectUri
can be defined under the "OAuth & Permissions" section of the website.
clientId
和 clientSecret
可以在 "应用凭证" 部分找到。
¥clientId
and clientSecret
can be found in the "App Credentials" section.
范围必须与 ':' 连接,因此只需创建一个长字符串。
¥Scopes must be joined with ':' so just create one long string.
导航至 "范围" 部分以启用范围。
¥Navigate to the "Scopes" section to enable scopes.
revocationEndpoint
不可用。
¥revocationEndpoint
is not available.
import * as React 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://slack.com/oauth/authorize',
tokenEndpoint: 'https://slack.com/api/oauth.access',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
scopes: ['emoji:read'],
redirectUri: makeRedirectUri({
scheme: 'your.app'
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
设置你的重定向 URI:你的项目 > 编辑设置 > 重定向 URI,请确保在进行更改后保存。
¥Setup your redirect URIs: Your project > Edit Settings > Redirect URIs, be sure to save after making changes.
网络开发:https://localhost:19006
¥Web dev: https://localhost:19006
重要的:确保 URL 末尾没有斜杠,除非在应用代码中使用 makeRedirectUri({ path: '/' })
手动更改。
¥Important: Ensure there's no slash at the end of the URL unless manually changed in the app code with makeRedirectUri({ path: '/' })
.
运行 expo start --web --https
以使用 https 运行,否则 auth 将无法工作。
¥Run expo start --web --https
to run with https, auth won't work otherwise.
独立/开发构建:your-scheme://
¥Standalone / development build: your-scheme://
方案应在 app.json expo.scheme: 'your-scheme'
中指定,然后使用 makeRedirectUri({ native: 'your-scheme://' })
添加到应用代码中)
¥Scheme should be specified in app.json expo.scheme: 'your-scheme'
, then added to the app code with makeRedirectUri({ native: 'your-scheme://' })
)
网页制作:https://yourwebsite.com
¥Web production: https://yourwebsite.com
将其设置为你部署的网站 URL 的值。
¥Set this to whatever your deployed website URL is.
了解有关 Spotify API 的更多信息。
¥Learn more about the Spotify API.
import * as React 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://accounts.spotify.com/authorize',
tokenEndpoint: 'https://accounts.spotify.com/api/token',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
scopes: ['user-read-email', 'playlist-modify-public'],
// To follow the "Authorization Code Flow" to fetch token after authorizationEndpoint
// this must be set to false
usePKCE: false,
redirectUri: makeRedirectUri({
scheme: 'your.app'
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
了解有关 Strava API 的更多信息。
¥Learn more about the Strava API.
"授权回调域" 指的是重定向 URI 的最终路径组成部分。前任:在 URI com.bacon.myapp://redirect
中,域将为 redirect
。
¥The "Authorization Callback Domain" refers to the final path component of your redirect URI. Ex: In the URI com.bacon.myapp://redirect
the domain would be redirect
.
Strava 不提供隐式身份验证流程。
¥No Implicit auth flow is provided by Strava.
import * as React 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://www.strava.com/oauth/mobile/authorize',
tokenEndpoint: 'https://www.strava.com/oauth/token',
revocationEndpoint: 'https://www.strava.com/oauth/deauthorize',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
scopes: ['activity:read_all'],
redirectUri: makeRedirectUri({
// the "redirect" must match your "Authorization Callback Domain" in the Strava dev console.
native: 'your.app://redirect',
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
Strava 不提供隐式身份验证流程,你应该将代码发送到服务器或无服务器函数来执行访问令牌交换。出于调试目的,你可以使用以下方法在客户端执行交换:
¥Strava doesn't provide an implicit auth flow, you should send the code to a server or serverless function to perform the access token exchange. For debugging purposes, you can perform the exchange client-side using the following method:
const { accessToken } = await AuthSession.exchangeCodeAsync(
{
clientId: request?.clientId,
redirectUri,
code: result.params.code,
extraParams: {
// You must use the extraParams variation of clientSecret.
// Never store your client secret on the client.
client_secret: 'CLIENT_SECRET',
},
},
{ tokenEndpoint: 'https://www.strava.com/oauth/token' }
);
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OpenID | 支持的 | 可用的 |
import * as React 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://<instance>.strivacity.com/oauth2/auth',
tokenEndpoint: 'https://<instance>.strivacity.com/oauth2/token',
revocationEndpoint: 'https://<instance>.strivacity.com/oauth2/revoke',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
scopes: ['openid', 'profile'],
redirectUri: makeRedirectUri();
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
你需要在 Twitch 账户上启用 2FA 才能创建应用。
¥You will need to enable 2FA on your Twitch account to create an application.
import * as React 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://id.twitch.tv/oauth2/authorize',
tokenEndpoint: 'https://id.twitch.tv/oauth2/token',
revocationEndpoint: 'https://id.twitch.tv/oauth2/revoke',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
redirectUri: makeRedirectUri({
scheme: 'your.app'
}),
scopes: ['user:read:email', 'analytics:read:games'],
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
你需要获得 Twitter 支持人员的批准才能使用 Twitter v2 API。
¥You will need to be approved by Twitter support before you can use the Twitter v2 API.
Web 似乎无法正常工作,Twitter 认证网站似乎阻止了弹出窗口,导致 useAuthRequest
的 response
始终为 {type: 'dismiss'}
。
¥Web does not appear to work, the Twitter authentication website appears to block the popup, causing the response
of useAuthRequest
to always be {type: 'dismiss'}
.
重定向示例:
¥Example redirects:
独立/开发构建:com.your.app://
¥Standalone / development build: com.your.app://
网页(开发 expo start --https
):https://localhost:19006
(无结尾斜杠)
¥Web (dev expo start --https
): https://localhost:19006
(no ending slash)
redirectUri
需要两个斜杠(://
)。
¥The redirectUri
requires two slashes (://
).
import * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
import { makeRedirectUri, useAuthRequest } from 'expo-auth-session';
import { Button, Platform } from 'react-native';
WebBrowser.maybeCompleteAuthSession();
// Endpoint
const discovery = {
authorizationEndpoint: "https://twitter.com/i/oauth2/authorize",
tokenEndpoint: "https://twitter.com/i/oauth2/token",
revocationEndpoint: "https://twitter.com/i/oauth2/revoke",
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
redirectUri: makeRedirectUri({
scheme: 'your.app',
}),
usePKCE: true,
scopes: [
"tweet.read",
],
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
网站 | 提供者 | PKCE | 自动发现 |
---|---|---|---|
获取你的配置 | OAuth 2.0 | 支持的 | 无法使用 |
redirectUri
需要两个斜杠(://
)。
¥The redirectUri
requires two slashes (://
).
scopes
可能很难获得批准。
¥scopes
can be difficult to get approved.
import * as React 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://login.uber.com/oauth/v2/authorize',
tokenEndpoint: 'https://login.uber.com/oauth/v2/token',
revocationEndpoint: 'https://login.uber.com/oauth/v2/revoke',
};
export default function App() {
const [request, response, promptAsync] = useAuthRequest(
{
clientId: 'CLIENT_ID',
scopes: ['profile', 'delivery'],
redirectUri: makeRedirectUri({
scheme: 'your.app'
}),
},
discovery
);
React.useEffect(() => {
if (response?.type === 'success') {
const { code } = response.params;
}
}, [response]);
return (
<Button
disabled={!request}
title="Login"
onPress={() => {
promptAsync();
}}
/>
);
}
¥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 (/
).
环境:
¥Environment:
裸工作流程
¥Bare workflow
npx expo prebuild
在 App 或 Play Store 中独立构建或在本地测试
¥Standalone builds in the App or Play Store or testing locally
安卓:eas build
或 npx expo run:android
¥Android: eas build
or npx expo run:android
iOS:eas build
或 npx expo run:ios
¥iOS: eas build
or npx expo run:ios
创造:在正确的环境中运行时使用 AuthSession.makeRedirectUri({ native: '<YOUR_URI>' })
选择原生。
¥Create: Use AuthSession.makeRedirectUri({ native: '<YOUR_URI>' })
to select native when running in the correct environment.
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 方案。
¥This link can often be created automatically but we recommend you define the scheme
property at least. The entire URL can be overridden in apps by passing the native
property. Often this will be used for providers like Google or Okta which require you to use a custom native URI redirect. You can add, list, and open URI schemes using npx uri-scheme
.
如果在弹出后更改 expo.scheme
,则需要使用 expo apply
命令将更改应用到原生项目,然后重建它们(yarn ios
、yarn android
)。
¥If you change the expo.scheme
after ejecting then you'll need to use the expo apply
command to apply the changes to your native project, then rebuild them (yarn ios
, yarn android
).
用法:promptAsync({ redirectUri })
¥Usage: promptAsync({ redirectUri })
¥Improving user experience
"登录流程" 是一件需要做好的重要事情,在很多情况下,这是用户 promise 再次使用你的应用的地方。糟糕的体验可能会导致用户在真正开始使用你的应用之前就放弃它。
¥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 * as React from 'react';
import * as WebBrowser from 'expo-web-browser';
function App() {
React.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
仍然支持用于旧版代码目的的隐式流。下面是隐式流程的示例实现。
¥expo-auth-session
still supports Implicit flow for legacy code purposes. Below is an example implementation of the Implicit flow.
import * as React 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
);
React.useEffect(() => {
if (response && response.type === 'success') {
const token = response.params.access_token;
}
}, [response]);
return <Button disabled={!request} onPress={() => promptAsync()} title="Login" />;
}
¥Storing data
在 iOS 和 Android 等原生平台上,你可以使用名为 expo-secure-store
的包(这与不安全的 AsyncStorage
不同)来保护本地访问令牌等内容。此软件包提供对 iOS 上的 密钥扣服务 和 Android 上的加密 SharedPreferences
的原生访问。没有与此功能相当的网络。
¥On native platforms like iOS, and Android you can secure things like access tokens locally using a package called expo-secure-store
(This is different to AsyncStorage
which is not secure). This package provides native access to keychain services on iOS and encrypted SharedPreferences
on Android. 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({});
React.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...
}