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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:24:52