NextAuth v5(Auth.js)+ Authentik:signOut无法彻底登出用户

前端开发 2026-07-11

我在一个使用Next.js App Router的应用中,使用NextAuth v5,并将Authentik作为OIDC提供者。

当用户点击“登出”时,我调用 signOut({ redirect: false }) 并重定向到 /login。NextAuth的会话cookie会被清除,但Authentik的浏览器会话仍然存在——如果有人在登录页输入相同的邮箱,Authentik会在不发送新的魔法链接的情况下自动认证他们。

I've tried the OIDC end-session redirect approach:

await signOut({ redirect: false });
const logoutUrl = new URL("https://auth.example.com/application/o/my-app/end-session/");
logoutUrl.searchParams.set("id_token_hint", idToken);
logoutUrl.searchParams.set("post_logout_redirect_uri", "https://myapp.example.com/login");
redirect(logoutUrl.toString());

但Authentik会显示它的“Default Invalidation Flow”界面,而不是重定向到 post\_logout\_redirect\_uri。如何让用户在NextAuth与 Authentik双方都彻底登出,并直接返回到我的登录页?

解决方案

端会话重定向的方法在Authentik上并不能可靠地工作——它的Default Invalidation Flow会拦截该请求,无论 post\_logout\_redirect\_uri 的具体含义为何。更可靠的做法是在服务端通过管理员API撤销Authentik会话,然后清除NextAuth的 cookie,并直接重定向回你的登录页面。

export async function revokeAuthentikSessions(email: string): Promise<void> {
  const userRes = await fetch(
    `${AUTHENTIK_BASE}/core/users/?email=${encodeURIComponent(email)}`,
    { headers: authHeaders() }
  );
  if (!userRes.ok) return;
  const { results } = await userRes.json();
  if (!results.length) return;

  // Iterate ALL users matching this email — if you use JIT provisioning,
  // multiple Authentik accounts may share the same email address. Taking
  // only results[0] will silently miss the active session.
  await Promise.all(
    results.map(async (u: { pk: number }) => {
      const sessRes = await fetch(
        `${AUTHENTIK_BASE}/core/authenticated_sessions/?user=${u.pk}`,
        { headers: authHeaders() }
      );
      if (!sessRes.ok) return;
      const { results: sessions } = await sessRes.json();
      await Promise.all(
        sessions.map((s: { uuid: string }) =>
          fetch(`${AUTHENTIK_BASE}/core/authenticated_sessions/${s.uuid}/`, {
            method: "DELETE",
            headers: authHeaders(),
          })
        )
      );
    })
  );
}

然后在你的登出服务器操作中:

action={async () => {
  "use server";
  const session = await auth();
  if (session?.user?.email) await revokeAuthentikSessions(session.user.email);
  await signOut({ redirect: false });
  redirect("/login");
}}

即时按需创建(JIT)用户的问题:如果你的应用在首次登录时按需创建Authentik用户(如 provider\_42 等),按邮箱查找会返回多个用户。results[0] 是非确定性的——活动会话可能属于与第一次返回的用户不同的那个用户。上面的循环可以处理这个问题。

带背景的完整写法:https://gist.github.com/speckofthecosmos/e8c471ee9a59f7d9494a854fff4074ad

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章