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

如何用expo-auth-session获取多资源AccessToken避免重复授权?

解决Expo Auth Session获取多资源AccessToken时的二次授权问题

核心问题原因

当前代码通过两次独立的useAuthRequest创建分离的授权流程,第二次请求无法复用第一次的登录会话上下文;即使设置了Prompt.None,由于缺少会话标识,Azure AD无法识别已登录用户,仍会触发二次授权。

推荐解决方案:用Refresh Token获取第二个资源的AccessToken

由于你已请求offline_access权限,第一次授权会返回refresh token,直接用它向token端点请求自研API的access token,无需再次引导用户授权。

修改后的完整代码

import { useContext, useState } from 'react';
import { exchangeCodeAsync, makeRedirectUri, Prompt, useAuthRequest, useAutoDiscovery } from 'expo-auth-session';
import { ProfileContext } from '../context/ProfileContext';
import { useQueryClient } from '@tanstack/react-query';

const useAuthentication = () => {
    const TENANT_ID = '<MY_TENANT_ID>';
    const CLIENT_ID = '<MY_CLIENT_ID>';
    const LOGIN_ENDPOINT = `https://login.microsoftonline.com/${TENANT_ID}/v2.0`;

    const SCOPES_GRAPH = ['openid', 'profile', 'email', 'offline_access'];
    const SCOPES_MY_API = [`${CLIENT_ID}/.default`];

    const queryClient = useQueryClient();
    const {
        accessToken,
        idToken,
        setAccessToken,
        setIdToken,
        myApiAccessToken,
        setMyApiAccessToken,
    } = useContext(ProfileContext);
    const [refreshToken, setRefreshToken] = useState(null);

    const discovery = useAutoDiscovery(LOGIN_ENDPOINT);
    const redirectUri = makeRedirectUri({
        scheme: undefined,
        path: 'auth',
    });
    const clientId = CLIENT_ID;
    const [request, , promptAsync] = useAuthRequest(
        {
            clientId,
            scopes: SCOPES_GRAPH,
            redirectUri,
            extraParams: {
                prompt: Prompt.SelectAccount,
            },
        },
        discovery,
    );

    const login = async () => {
        try {
            const codeResponse = await promptAsync();
            if (request && codeResponse?.type === 'success' && discovery) {
                const res = await exchangeCodeAsync(
                    {
                        clientId,
                        code: codeResponse.params.code,
                        extraParams: request.codeVerifier
                            ? { code_verifier: request.codeVerifier }
                            : undefined,
                        redirectUri,
                    },
                    discovery,
                );
                setAccessToken(res.accessToken);
                setIdToken(res.idToken);
                setRefreshToken(res.refreshToken);
                await fetchSecondToken(res.refreshToken);
            }
        } catch (error) {
            console.log('Failed to login:', error);
        }
    };

    const fetchSecondToken = async (refreshToken) => {
        if (!discovery || !refreshToken) return;

        try {
            const tokenResponse = await fetch(discovery.tokenEndpoint, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: new URLSearchParams({
                    client_id: CLIENT_ID,
                    grant_type: 'refresh_token',
                    refresh_token: refreshToken,
                    scope: SCOPES_MY_API.join(' '),
                    redirect_uri: redirectUri,
                }),
            });

            const data = await tokenResponse.json();
            if (data.access_token) {
                setMyApiAccessToken(data.access_token);
                if (data.refresh_token) {
                    setRefreshToken(data.refresh_token);
                }
            } else {
                console.error('Failed to retrieve My API access token:', data.error_description || data.error);
            }
        } catch (error) {
            console.log('Failed to fetch second token:', error);
        }
    };

    return { login };
};

export default useAuthentication;

关键修改说明

  1. 新增refreshToken状态,保存第一次授权返回的刷新令牌
  2. 移除第二次useAuthRequest及相关授权跳转逻辑,改用refresh token直接调用token端点
  3. 处理Azure AD可能返回的新refresh token,保证令牌刷新链的有效性

备选方案:一次性请求所有Scope

如果自研API和Microsoft Graph支持合并授权,可直接将两个Scope数组合并,一次授权获取可访问双资源的token:

const SCOPES_COMBINED = [...SCOPES_GRAPH, ...SCOPES_MY_API];

然后在首次useAuthRequest中使用合并后的Scope即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 03:13:18