You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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要求必须添加openid scope才能正常获取用户信息
  • 重定向URI配置:确保REDIRECT_URI已经在Google Cloud Console的OAuth2客户端设置中添加(包括开发环境和生产环境的URI)
  • Facebook认证适配:流程完全一致,只需将Discovery的issuer改为https://www.facebook.com,客户端ID换成Facebook的,scopes调整为['public_profile', 'email']即可

内容的提问来源于stack exchange,提问作者madsongr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:59:50