在Next-Auth(React)中如何从Azure AD Token获取Microsoft Graph Token
解决Next-Auth AzureADProvider同时获取自有API和Microsoft Graph Token的问题
问题根源
一个Access Token的受众(aud)只能对应一个资源,你当前获取的Token是针对自有API(api://ownscope)的,无法直接用于访问Microsoft Graph(受众是https://graph.microsoft.com)。同时请求多个资源的Scope时,Azure AD默认返回第一个资源的Token,因此需要额外获取Graph专用的Token。
解决方案:使用On-Behalf-Of (OBO)流程获取Graph Token
OBO流程允许你的后端服务,用用户已有的自有API Token,向Azure AD请求针对Microsoft Graph的Access Token,这样就能同时拥有两个资源的访问权限。
步骤1:确认Azure AD应用配置
确保你的应用注册已添加Microsoft Graph的User.Read委托权限,并且完成了管理员同意(租户级应用需此操作)。
步骤2:实现OBO Token请求函数
在Next-Auth配置文件中添加以下函数,用于获取Graph专属Token:
async function getGraphAccessToken(ownApiToken: string, tenantId: string, clientId: string, clientSecret: string): Promise<string> { const tokenEndpoint = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`; const formData = new URLSearchParams(); formData.append('grant_type', 'urn:ietf:params:oauth:grant-type:jwt-bearer'); formData.append('client_id', clientId); formData.append('client_secret', clientSecret); formData.append('assertion', ownApiToken); formData.append('scope', 'https://graph.microsoft.com/user.read'); formData.append('requested_token_use', 'on_behalf_of'); const response = await fetch(tokenEndpoint, { method: 'POST', body: formData, headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, }); if (!response.ok) { throw new Error(`OBO Token请求失败: ${await response.text()}`); } const data = await response.json(); return data.access_token; }
步骤3:修改Next-Auth的JWT回调
在jwt回调中,先用自有API的Token通过OBO流程获取Graph Token,再用该Token调用Graph API:
export const authOptions: NextAuthOptions = { providers: [ AzureADProvider({ name: 'Azure Active Directory', clientId: process.env.AZURE_CLIENT_ID ?? 'undefined', clientSecret: process.env.AZURE_CLIENT_SECRET ?? 'undefined', tenantId: process.env.AZURE_TENANT_ID, authorization: { params: { scope: 'api://ownscope/general openid profile email', // 无需在这里加Graph的scope,OBO请求时会单独指定 }, }, }), ], theme: { colorScheme: 'light', }, callbacks: { async signIn() { return true; }, async jwt({ token, account }) { if (account) { // 保存自有API的Token,用于后续访问自有服务 token.own_access_token = account.access_token; try { // 通过OBO流程获取Graph Token const graphAccessToken = await getGraphAccessToken( account.access_token, process.env.AZURE_TENANT_ID!, process.env.AZURE_CLIENT_ID!, process.env.AZURE_CLIENT_SECRET! ); // 使用Graph Token调用API获取用户信息 const graphClient = getGraphClient({ access_token: graphAccessToken }); token.graphdata = await graphClient.api('/me').get(); } catch (err) { console.error('获取Graph数据失败'); console.error(err); } } return token; }, async session({ session, token }) { session.userdata = ToUser(token.graphdata); // 若前端需要自有API的Token,可在此添加到session中 session.own_access_token = token.own_access_token; return session; }, }, events: { async signIn(message) { console.log('登录成功: %s', JSON.stringify(message.user.name)); }, async session(message) { console.log('会话活跃: %s', JSON.stringify(message?.session?.user?.name)); }, }, }; export default NextAuth(authOptions);
关键说明
- OBO流程要求应用具备
Delegated Permission,且用户已完成认证(拥有有效的自有API Token)。 - 自有API的Token需保存到JWT中,方便后续访问自有服务;Graph Token仅用于获取用户数据,可按需保存。
- 确保环境变量
AZURE_TENANT_ID、AZURE_CLIENT_ID、AZURE_CLIENT_SECRET配置正确。
内容的提问来源于stack exchange,提问作者MrAnderson
相关产品推荐
相关产品推荐

