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

NextAuth Credential Provider对接Azure后端的令牌存储与使用问题

问题描述

我是Next.js和NextAuth新手,目前我们已有部署在Azure上的.NET后端,正计划从React前端迁移至Next.js。我尝试使用CredentialsProvider实现认证,通过Axios调用Azure后端获取了access_token、refresh_token、endpoint_tokens等所需令牌,但不清楚如何让这些令牌在NextAuth体系中生效。此外,每次调用Azure后端都需要携带这些令牌,由于服务端无法操作localStorage,我想询问是否可以通过修改Session来存储这些令牌。

相关代码

export const authProviders: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENTID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    }),
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        username: {
          label: "Username",
          type: "text",
          placeholder: "user",
          value: "Administrator",
        },
        password: {
          label: "Password",
          type: "password",
          placeholder: "pass",
          value: "password",
        },
      },
      async authorize(credentials, req) {
        if (!credentials || !credentials?.username || !credentials?.password) {
          return null;
        }
        try {
          const { data, cookies } = await createBrowserToken(
            credentials.username,
            credentials.password
          );

          if (data) {
            console.log("response for tokens", data);
            const { access_token, refresh_token, expires_in, endpoint_tokens } =
              data;
            const getUserToken = endpoint_tokens.find(
              (token: any) => token.endpoint_route === "GETapi/v1/Users/GetUser"
            ).access_token;
            console.log(
              "response tokensss",
              getUserToken,
              access_token,
              cookies
            );
            if (cookies !== null) {
              const user = await fetchUser(getUserToken, access_token, cookies);
              console.log("user", user);
              if (user) {
                return {
                  id: user.id,
                  name: `${user.firstName}-${user.lastName}`,
                  email: user.email,
                };
              } else return null;
            }
            return null;
          } else {
            return null;
          }
        } catch (error) {
          console.error("Error during login:", error);
          return null;
        }
      }
    })
  ]
}
解决方案

1. 在Session中存储自定义令牌

完全可以通过NextAuth的Session存储这些令牌,核心是修改jwt和session两个回调:

  • jwt回调:用户认证成功后,将令牌附加到JWT对象(NextAuth默认用JWT保存会话数据,服务端/客户端均可访问)
  • session回调:把JWT中的令牌映射到Session对象,让前端能通过useSession获取

修改你的authProviders配置,添加以下回调:

export const authProviders: NextAuthOptions = {
  providers: [
    // 保留原有GoogleProvider和CredentialsProvider配置
  ],
  callbacks: {
    async jwt({ token, user }) {
      // 首次登录时注入令牌数据
      if (user) {
        token.access_token = user.access_token;
        token.refresh_token = user.refresh_token;
        token.endpoint_tokens = user.endpoint_tokens;
        token.expires_in = user.expires_in;
      }
      return token;
    },
    async session({ session, token }) {
      // 将JWT中的令牌同步到Session
      session.user.access_token = token.access_token as string;
      session.user.refresh_token = token.refresh_token as string;
      session.user.endpoint_tokens = token.endpoint_tokens as any[];
      session.user.expires_in = token.expires_in as number;
      return session;
    }
  },
  // 显式声明Session策略为JWT(默认就是JWT,可选)
  session: {
    strategy: "jwt"
  }
}

2. 修改authorize方法返回值

在authorize中把令牌和用户信息一起返回,这样jwt回调才能拿到令牌数据:

async authorize(credentials, req) {
  if (!credentials || !credentials?.username || !credentials?.password) {
    return null;
  }
  try {
    const { data, cookies } = await createBrowserToken(
      credentials.username,
      credentials.password
    );

    if (data) {
      const { access_token, refresh_token, expires_in, endpoint_tokens } = data;
      const getUserToken = endpoint_tokens.find(
        (token: any) => token.endpoint_route === "GETapi/v1/Users/GetUser"
      ).access_token;
      
      if (cookies !== null) {
        const user = await fetchUser(getUserToken, access_token, cookies);
        if (user) {
          // 返回用户信息+所有令牌数据
          return {
            id: user.id,
            name: `${user.firstName}-${user.lastName}`,
            email: user.email,
            access_token,
            refresh_token,
            expires_in,
            endpoint_tokens
          };
        } else return null;
      }
      return null;
    } else {
      return null;
    }
  } catch (error) {
    console.error("Error during login:", error);
    return null;
  }
}

3. 在API调用中使用令牌

  • 客户端组件:通过useSession获取令牌,携带到请求头:
import { useSession } from "next-auth/react";

const { data: session } = useSession();

// 调用Azure后端
axios.get("https://your-azure-backend/api/endpoint", {
  headers: {
    Authorization: `Bearer ${session?.user.access_token}`
  }
});
  • 服务端组件/API路由:通过getServerSession获取令牌(服务端无法用useSession):
import { getServerSession } from "next-auth/next";
import { authProviders } from "@/path-to-your-auth-config";

export async function GET() {
  const session = await getServerSession(authProviders);
  if (!session) {
    return new Response("Unauthorized", { status: 401 });
  }

  const response = await fetch("https://your-azure-backend/api/endpoint", {
    headers: {
      Authorization: `Bearer ${session.user.access_token}`
    }
  });

  const data = await response.json();
  return Response.json(data);
}

4. 自动刷新令牌(可选)

在jwt回调中检查令牌过期时间,自动调用后端刷新接口更新令牌:

async jwt({ token, user }) {
  // 首次登录注入令牌和过期时间戳
  if (user) {
    token.access_token = user.access_token;
    token.refresh_token = user.refresh_token;
    token.expires_at = Date.now() + user.expires_in * 1000;
    token.endpoint_tokens = user.endpoint_tokens;
    return token;
  }

  // 令牌未过期直接返回
  if (Date.now() < token.expires_at) {
    return token;
  }

  // 令牌过期,调用刷新接口
  try {
    const response = await axios.post("https://your-azure-backend/refresh-token", {
      refresh_token: token.refresh_token
    });

    const { access_token, expires_in, endpoint_tokens } = response.data;
    return {
      ...token,
      access_token,
      expires_at: Date.now() + expires_in * 1000,
      endpoint_tokens
    };
  } catch (error) {
    // 刷新失败,标记错误触发重新登录
    console.error("Token refresh failed:", error);
    return { ...token, error: "RefreshTokenError" };
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 19:38:13