NextAuth.js+Keycloak:AccessToken过期时用户登出异常排查
项目简介
我的Next.JS 14.2.3项目中,使用NextAuth.JS v4对接SSO Keycloak OAuth2。Keycloak服务器由第三方运维,我仅持有issuer链接及必要配置信息。
问题描述
包含我司站点在内,共有2个站点使用该Keycloak端点。我推测问题源于此(可能有误):用户在一个站点登录获取AccessToken后,访问我司站点时该AccessToken已失效,导致所有请求均无法返回200。我认为Token生命周期与本地Session周期一致。
已尝试方案
最初考虑Token刷新,但遇到NextAuth.JS无法持久化刷新后的Token的问题,导致AccessToken更新困难。虽有临时解决办法,但我更倾向于直接让用户登出。
随后我想到,若Token与Session生命周期不匹配且无法刷新,可检查Token是否过期,若过期则登出用户。
代码实现
options.ts:
import { jwtDecode } from 'jwt-decode'; import { NextAuthOptions, Session } from 'next-auth'; import { JWT } from 'next-auth/jwt'; import KeycloakProvider from 'next-auth/providers/keycloak'; import { signOut } from 'next-auth/react'; import { keycloakEnvs } from '@/envs'; export const options: NextAuthOptions = { session: { maxAge: 24 * 60 * 60, updateAge: 12 * 60 * 60, }, secret: process.env.NEXTAUTH_SECRET, pages: { error: '/auth/error', }, providers: [ KeycloakProvider({ clientSecret: keycloakEnvs.CLIENT_SECRET ?? '', clientId: keycloakEnvs.CLIENT_ID ?? '', issuer: keycloakEnvs.ISSUER ?? '', }), ], events: { async signOut({ token }: { token: JWT }) { const logOutUrl = new URL( `${keycloakEnvs.ISSUER}/protocol/openid-connect/logout`, ); if (token.id_token) { logOutUrl.searchParams.set('id_token_hint', token.id_token as string); } else { logOutUrl.searchParams.set('client_id', keycloakEnvs.CLIENT_ID || ''); } logOutUrl.searchParams.set( 'post_logout_redirect_uri', process.env.NEXTAUTH_URL || '', ); await fetch(logOutUrl.toString()); }, }, callbacks: { async jwt({ token, account }) { if (account) { token.access_token = account.access_token; token.id_token = account.id_token; token.provider = account.provider; const { roles = [] } = jwtDecode<{ roles: string[] }>(account.access_token || '') || {}; token.isAdmin = roles.includes('ROLE_ADMIN'); } if (token.exp && +token.exp > Date.now() / 1000) { const logOutUrl = new URL( `${keycloakEnvs.ISSUER}/protocol/openid-connect/logout`, ); if (token.id_token) { logOutUrl.searchParams.set('id_token_hint', token.id_token as string); } else { logOutUrl.searchParams.set('client_id', keycloakEnvs.CLIENT_ID || ''); } logOutUrl.searchParams.set( 'post_logout_redirect_uri', process.env.NEXTAUTH_URL || '', ); // console.log({ logOutUrl }); try { const response = await fetch(logOutUrl.toString(), { method: 'POST', body: JSON.stringify({ token: token.id_token }), }); console.log({ response }); if (!response.ok) { console.error('Failed to log out:', response.statusText); } await signOut(); } catch (error) { console.error('Error during logout:', error); } } return token; }, async session({ session, token }: { session: Session; token: JWT }) { if (token) { session.user!.access_token = token.access_token as string; session.user!.isAdmin = token.isAdmin as boolean; } else { await signOut(); } return session; }, }, };
登出逻辑还尝试过:
await fetch(logOutUrl.toString());
console.log({ response })返回:
{ response: Response { status: 200, statusText: '', ...
可见请求已执行,但未触发实际登出操作。
预期效果
当AccessToken过期时,自动将用户从本地Session和Keycloak会话中登出。
内容的提问来源于stack exchange,提问作者Lvasche
相关产品推荐
相关产品推荐

