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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 04:42:14