Expo SDK升级至50后React Native Auth Session认证失效求助
Expo SDK 50 中基于浏览器的Google认证适配问题
背景:SDK48下的工作代码
此前使用Expo SDK 48时,基于浏览器的Google认证运行正常,核心代码如下:
type AuthResponse = AuthSession.AuthSessionResult & { params: { access_token?: string; error?: string; }; type: string; } const AuthProvider = ({ children }: any) => { /* Google */ const signInWithGoogle = async (navigation: any) => { try { const SCOPE = encodeURI("email profile"); const RESPONSE_TYPE = 'token'; const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?client_id=${GOOGLE_WEB_CLIENT_ID}&redirect_uri=${REDIRECT_URI}&response_type=${RESPONSE_TYPE}&scope=${SCOPE}`; const { type, params } = await AuthSession.startAsync({ authUrl }) as AuthResponse; if (type === 'success') { checkGoogleUserInfo(params.access_token, navigation); } else { return; } } catch (error) { console.log("Error retrieving token data from Google ==>> ", error); } } }
升级后遇到的问题
将Expo升级至SDK 50并更新expo-auth-session到5.4.0后,AuthSession.startAsync()停止工作。尝试官方新方法时,出现错误:Possible unhandled promise rejection: SyntaxError: JSON Parse error: Unexpected character: <
失效代码如下:
const endpoint = "https://accounts.google.com/o/oauth2/v2/auth"; const clientId: any = GOOGLE_WEB_CLIENT_ID; const redirectUri: any = REDIRECT_URI; const [discovery, setDiscovery] = useState({}); useEffect(() => { async function loadDiscovery() { // 此处引发Promise拒绝错误 const getDiscovery = await fetchDiscoveryAsync(endpoint).then((discovery) => setDiscovery({ discovery })); console.log("get getDiscovery >>>>>>> " + JSON.stringify(getDiscovery)); } loadDiscovery(); }, []); const [request, response, promptAsync] = useAuthRequest({ clientId, scopes: ['email', 'profile'], redirectUri }, discovery); useEffect(() => { console.log(discovery); if (!discovery) { console.log("no discovery"); return; } if (response?.type === "error") { console.log("response type error: " + (response.params.error || "something went wrong")) return } if (!discovery || (response?.type !== "success")) { console.log("no discovery and no response type"); return; } const code = response.params.code; if (!code) { console.log("no code"); return; } }, [response, discovery]);
编辑补充:获取到Discovery但Response为null
将endpoint改为https://accounts.google.com后成功获取到Discovery文档,但response仍为null。Discovery内容如下:
{ "discovery": { "discoveryDocument": { "issuer": "https://accounts.google.com", "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth", "device_authorization_endpoint": "https://oauth2.googleapis.com/device/code", "token_endpoint": "https://oauth2.googleapis.com/token", "userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo", "revocation_endpoint": "https://oauth2.googleapis.com/revoke", "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs", "response_types_supported": [ "code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none" ], "subject_types_supported": ["public"], "id_token_signing_alg_values_supported": ["RS256"], "scopes_supported": ["openid", "email", "profile"], "token_endpoint_auth_methods_supported": ["client_secret_post", "client_secret_basic"], "claims_supported": [ "aud", "email", "email_verified", "exp", "family_name", "given_name", "iat", "iss", "locale", "name", "picture", "sub" ], "code_challenge_methods_supported": ["plain", "S256"], "grant_types_supported": [ "authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:jwt-bearer" ] }, "authorizationEndpoint": "https://accounts.google.com/o/oauth2/v2/auth", "tokenEndpoint": "https://oauth2.googleapis.com/token", "revocationEndpoint": "https://oauth2.googleapis.com/revoke", "userInfoEndpoint": "https://openidconnect.googleapis.com/v1/userinfo" } }
解决方案
1. 核心结论:仍然可以使用基于浏览器的Google/Facebook认证
SDK50只是调整了expo-auth-session的用法,弃用了startAsync,改用useAuthRequest + promptAsync的组合,同时需要正确处理Discovery文档和认证流程。
2. 修复Discovery文档的状态管理问题
你之前的代码把获取到的Discovery包装在了{ discovery }对象里,导致useAuthRequest无法识别正确的格式。需要直接将获取到的Discovery对象存入state:
useEffect(() => { async function loadDiscovery() { try { // 传入Google的issuer,而非授权端点 const discoveryDoc = await fetchDiscoveryAsync('https://accounts.google.com'); setDiscovery(discoveryDoc); // 直接赋值,不要嵌套 } catch (error) { console.error('加载Discovery失败:', error); } } loadDiscovery(); }, []);
3. 触发认证流程并处理响应
之前的代码没有调用promptAsync,所以不会弹出认证页面,response自然为null。需要在用户操作(比如按钮点击)时调用promptAsync,并在response返回后完成token交换和用户信息获取:
完整修正后的代码示例:
import { useState, useEffect } from 'react'; import { Button } from 'react-native'; import { useAuthRequest, fetchDiscoveryAsync, exchangeCodeAsync } from 'expo-auth-session'; const GOOGLE_WEB_CLIENT_ID = '你的Google客户端ID'; const REDIRECT_URI = '你的重定向URI'; // 需在Google Cloud Console中配置 const AuthProvider = ({ children }: any) => { const [discovery, setDiscovery] = useState<any>(null); const [request, response, promptAsync] = useAuthRequest( { clientId: GOOGLE_WEB_CLIENT_ID, scopes: ['openid', 'email', 'profile'], // 必须包含openid redirectUri: REDIRECT_URI, responseType: 'code', // 授权码模式,更安全 }, discovery ); // 加载Discovery文档 useEffect(() => { async function loadDiscovery() { try { const discoveryDoc = await fetchDiscoveryAsync('https://accounts.google.com'); setDiscovery(discoveryDoc); } catch (error) { console.error('加载Discovery失败:', error); } } loadDiscovery(); }, []); // 处理认证响应 useEffect(() => { if (response?.type === 'success') { const { code } = response.params; // 用授权码交换access token async function exchangeToken() { try { const tokenData = await exchangeCodeAsync( { clientId: GOOGLE_WEB_CLIENT_ID, code, redirectUri: REDIRECT_URI, }, discovery ); const { access_token } = tokenData; // 获取用户邮箱等信息 const userInfo = await fetch('https://openidconnect.googleapis.com/v1/userinfo', { headers: { Authorization: `Bearer ${access_token}` }, }).then(res => res.json()); console.log('用户信息:', userInfo); // 这里可以添加登录成功后的逻辑,比如导航到主页、保存用户信息等 } catch (error) { console.error('Token交换或用户信息获取失败:', error); } } exchangeToken(); } else if (response?.type === 'error') { console.error('认证错误:', response.params.error); } }, [response, discovery]); return ( <> {/* 只有当Discovery加载完成后才显示登录按钮 */} {discovery && <Button title="Google登录" onPress={() => promptAsync()} />} {children} </> ); }; export default AuthProvider;
4. 关键注意事项
- Scope必须包含openid:Google的OAuth2要求必须添加
openidscope才能正常获取用户信息 - 重定向URI配置:确保
REDIRECT_URI已经在Google Cloud Console的OAuth2客户端设置中添加(包括开发环境和生产环境的URI) - Facebook认证适配:流程完全一致,只需将Discovery的issuer改为
https://www.facebook.com,客户端ID换成Facebook的,scopes调整为['public_profile', 'email']即可
内容的提问来源于stack exchange,提问作者madsongr
相关产品推荐
相关产品推荐

