如何在Next-Auth/Auth.js中获取Azure AD刷新令牌并自动续期
问题:Azure AD + NextAuth 无法获取刷新令牌(refresh token)
我已经花了两天时间尝试获取Azure AD的刷新令牌(refresh token),使用下方配置后,响应结果里始终没有刷新令牌。我希望能自动获取新的刷新令牌,避免用户重复登录。
配置代码
import NextAuth, { NextAuthOptions } from "next-auth"; import AzureADProvider from "next-auth/providers/azure-ad"; export const authOptions: NextAuthOptions = { providers: [ AzureADProvider({ clientId: `${process.env.AZURE_AD_CLIENT_ID}`, clientSecret: `${process.env.AZURE_AD_CLIENT_SECRET}`, tenantId: process.env.AZURE_AD_TENANT_ID, authorization: { params: { scope: "offline_access openid profile email Application.ReadWrite.All Directory.ReadWrite.All " + "Group.ReadWrite.All GroupMember.ReadWrite.All User.Read User.ReadWrite.All", }, }, }), ], callbacks: { async jwt({ token, user, account, profile }) { // Persist the OAuth access_token to the token right after signin console.log("JWT token", token); if (account) { console.log("account", token); token.accessToken = account.access_token; } return token; }, }, };
得到的响应
JWT token { name: 'name', picture: null, sub: 'X6_CatcEVmqbZlsN91oFDhdV3QUVe-....', accessToken: 'token', iat: 1708110693, exp: 1710702693, jti: '8eb36962-5998-4950-b0c9-7...' }
解决方案
1. 修复JWT回调,保存Refresh Token
现有回调仅保存了access token,未处理refresh token。需要在首次登录时,将account中的refresh_token也存入token对象。
2. 确保Azure AD应用配置正确
登录Azure门户进入目标应用注册:
- 转到身份验证 -> 平台配置,添加Web平台并勾选授权码流(refresh token仅在授权码流场景下返回)。
- 转到API权限,确认
offline_access权限已添加并完成管理员同意(租户级应用必须由管理员授权)。
3. 添加Token自动刷新逻辑
当access token过期时,用refresh token调用Azure AD令牌端点,获取新的access token和refresh token(Azure AD的refresh token使用一次后失效,需保存新返回的refresh token)。
修改后的完整代码
import NextAuth, { NextAuthOptions } from "next-auth"; import AzureADProvider from "next-auth/providers/azure-ad"; export const authOptions: NextAuthOptions = { providers: [ AzureADProvider({ clientId: `${process.env.AZURE_AD_CLIENT_ID}`, clientSecret: `${process.env.AZURE_AD_CLIENT_SECRET}`, tenantId: process.env.AZURE_AD_TENANT_ID, authorization: { params: { scope: "offline_access openid profile email Application.ReadWrite.All Directory.ReadWrite.All Group.ReadWrite.All GroupMember.ReadWrite.All User.Read User.ReadWrite.All", response_type: "code" // 显式指定授权码流 }, }, }), ], callbacks: { async jwt({ token, user, account, profile }) { // 首次登录时,保存access token、refresh token及过期时间 if (account && user) { return { accessToken: account.access_token, refreshToken: account.refresh_token, accessTokenExpires: account.expires_at ? account.expires_at * 1000 : Date.now() + 3600 * 1000, user, }; } // access token未过期,直接返回现有token if (Date.now() < (token.accessTokenExpires as number)) { return token; } // access token过期,执行刷新逻辑 return await refreshAccessToken(token); }, }, }; // 封装刷新token的函数 async function refreshAccessToken(token: any) { try { const tokenEndpoint = `https://login.microsoftonline.com/${process.env.AZURE_AD_TENANT_ID}/oauth2/v2.0/token`; const response = await fetch(tokenEndpoint, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: process.env.AZURE_AD_CLIENT_ID!, client_secret: process.env.AZURE_AD_CLIENT_SECRET!, grant_type: "refresh_token", refresh_token: token.refreshToken!, scope: "offline_access openid profile email Application.ReadWrite.All Directory.ReadWrite.All Group.ReadWrite.All GroupMember.ReadWrite.All User.Read User.ReadWrite.All", }), }); const refreshedTokens = await response.json(); if (!response.ok) throw refreshedTokens; return { ...token, accessToken: refreshedTokens.access_token, refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, // 优先使用新返回的refresh token accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, }; } catch (error) { console.error("刷新令牌失败:", error); // 刷新失败时标记错误,后续可引导用户重新登录 return { ...token, error: "RefreshAccessTokenError", }; } }
内容的提问来源于stack exchange,提问作者Yasir
相关产品推荐
相关产品推荐

