如何用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;
关键修改说明
- 新增
refreshToken状态,保存第一次授权返回的刷新令牌 - 移除第二次
useAuthRequest及相关授权跳转逻辑,改用refresh token直接调用token端点 - 处理Azure AD可能返回的新refresh token,保证令牌刷新链的有效性
备选方案:一次性请求所有Scope
如果自研API和Microsoft Graph支持合并授权,可直接将两个Scope数组合并,一次授权获取可访问双资源的token:
const SCOPES_COMBINED = [...SCOPES_GRAPH, ...SCOPES_MY_API];
然后在首次useAuthRequest中使用合并后的Scope即可。
内容的提问来源于stack exchange,提问作者Henrik Solum
相关产品推荐
相关产品推荐

