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

如何让NextAuth识别Keycloak Cookie,实现Next与React应用会话共享?

解决同子域下NextAuth+Keycloak与React Keycloak应用的会话同步问题

核心原因

你判断的没错:NextAuth会维护自己独立的会话Cookie(默认是next-auth.session-token),不会主动识别Keycloak原生的会话Cookie,这就导致React应用登录后,Next应用无法感知已存在的Keycloak会话。

可行解决方案

1. 让NextAuth主动验证Keycloak会话Cookie

在NextAuth的配置文件([...nextauth].js)中,通过自定义session和jwt回调,每次请求时检查Keycloak的会话Cookie,调用Keycloak官方接口验证会话有效性,同步到NextAuth的会话体系中。

示例代码:

import NextAuth from "next-auth";
import KeycloakProvider from "next-auth/providers/keycloak";

export default NextAuth({
  providers: [
    KeycloakProvider({
      clientId: process.env.KEYCLOAK_CLIENT_ID,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
      issuer: process.env.KEYCLOAK_ISSUER,
    }),
  ],
  callbacks: {
    async session({ session, req }) {
      // 从请求头中提取Keycloak会话Cookie(名称根据你的Keycloak配置调整)
      const keycloakSessionCookie = req.headers.cookie?.match(/KEYCLOAK_SESSION=([^;]+)/)?.[1];
      
      if (keycloakSessionCookie) {
        // 调用Keycloak用户信息接口验证会话有效性
        const userInfoRes = await fetch(`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/userinfo`, {
          headers: { Cookie: `KEYCLOAK_SESSION=${keycloakSessionCookie}` },
          credentials: "include",
        });
        
        if (userInfoRes.ok) {
          const userInfo = await userInfoRes.json();
          // 将Keycloak用户信息同步到NextAuth会话
          session.user = { ...session.user, ...userInfo };
          session.isKeycloakAuthenticated = true;
        } else {
          // 会话无效,清除NextAuth会话
          session = null;
        }
      }
      
      return session;
    },
    async jwt({ token, account }) {
      // 可选:将Keycloak的token同步到NextAuth的JWT中
      if (account) {
        token.accessToken = account.access_token;
        token.refreshToken = account.refresh_token;
      }
      return token;
    },
  },
  // 配置NextAuth Cookie与Keycloak Cookie同域
  cookies: {
    sessionToken: {
      name: `__Secure-next-auth.session-token`,
      domain: `.your-subdomain.com`, // 设置为父域,确保同子域应用均可访问
      path: "/",
      secure: true,
      httpOnly: true,
      sameSite: "lax",
    },
  },
});

2. 让NextAuth完全依赖Keycloak会话,放弃自身会话管理

自定义NextAuth的authorize函数,每次认证流程都直接验证Keycloak的会话Cookie,不再依赖NextAuth自己的会话机制。

示例代码:

export default NextAuth({
  providers: [
    KeycloakProvider({
      clientId: process.env.KEYCLOAK_CLIENT_ID,
      clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,
      issuer: process.env.KEYCLOAK_ISSUER,
    }),
  ],
  callbacks: {
    async authorize(credentials, req) {
      // 提取Keycloak会话Cookie
      const keycloakCookie = req.headers.cookie?.match(/KEYCLOAK_SESSION=([^;]+)/)?.[1];
      if (!keycloakCookie) return null;

      // 调用Keycloak的token introspect接口验证会话有效性
      const introspectRes = await fetch(`${process.env.KEYCLOAK_ISSUER}/protocol/openid-connect/token/introspect`, {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
          Authorization: `Basic ${Buffer.from(`${process.env.KEYCLOAK_CLIENT_ID}:${process.env.KEYCLOAK_CLIENT_SECRET}`).toString("base64")}`,
        },
        body: `token=${keycloakCookie}`,
      });

      const introspectData = await introspectRes.json();
      if (introspectData.active) {
        // 返回用户信息,NextAuth将基于此创建会话
        return { id: introspectData.sub, name: introspectData.name, email: introspectData.email };
      } else {
        // 会话无效,拒绝认证
        return null;
      }
    },
  },
});

3. 跨应用同步会话状态(快速实现但需注意安全)

利用同子域的localStorage或sessionStorage,在React应用登录成功后将Keycloak的token信息存入共享存储,Next应用在初始化时检查该存储,手动触发NextAuth的登录流程。

  • React应用登录成功回调:
// React应用中Keycloak登录成功的钩子
keycloak.onAuthSuccess = () => {
  localStorage.setItem("keycloak-access-token", keycloak.token);
  localStorage.setItem("keycloak-refresh-token", keycloak.refreshToken);
};
  • Next应用监听存储变化同步会话:
// 在Next应用的_app.js中添加监听逻辑
import { signIn, signOut } from "next-auth/react";
import { useEffect } from "react";

function MyApp({ Component, pageProps }) {
  useEffect(() => {
    const handleStorageChange = (e) => {
      if (e.key === "keycloak-access-token" && e.newValue) {
        // 手动触发NextAuth登录
        signIn("keycloak", { redirect: false });
      } else if (e.key === "keycloak-access-token" && !e.newValue) {
        // 登出时同步清除NextAuth会话
        signOut({ redirect: false });
      }
    };

    window.addEventListener("storage", handleStorageChange);
    return () => window.removeEventListener("storage", handleStorageChange);
  }, []);

  return <Component {...pageProps} />;
}

export default MyApp;

注意:此方法存在XSS风险,因为localStorage可被脚本读取,建议仅在内部系统使用,同时配合Keycloak的HttpOnly Cookie做二次验证。

4. 统一Keycloak会话Cookie的域配置

确保Keycloak设置的会话Cookie的Domain为父域(比如.yourdomain.com),让两个同子域应用都能读取到该Cookie。同时在NextAuth配置中,允许使用Keycloak会话进行认证。

  • Keycloak后台配置:进入对应客户端的Settings,设置Access Type为confidential或public,Valid Redirect URIs包含两个应用的地址,Web Origins设置为父域(比如https://*.yourdomain.com)。
  • NextAuth的Keycloak Provider配置中,确保issuer正确,并且请求Keycloak接口时开启credentials: include以携带Cookie。

注意事项

  • 所有Cookie必须设置为同父域,且SameSite属性设置为Lax或None(跨域场景),HTTPS环境下必须开启Secure属性。
  • 调用Keycloak接口时,要确保客户端拥有足够权限(比如view-users角色)。
  • 定期清理过期会话,避免无效Cookie残留。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 10:07:05