如何让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
相关产品推荐
相关产品推荐

