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

Next.js集成AWS Cognito Google登录正常但登出不彻底

问题:AWS Cognito + Google身份提供商登出不彻底,自动复用Google会话

我在Next.js应用中使用AWS Cognito,配置Google作为身份提供商,采用AWS托管登录UI。登录功能正常,能获取令牌且会话可持久化,但调用登出URL后,浏览器完成重定向,用户却未完全登出。再次点击“Sign in with Google”时,Google仍认为用户处于已认证状态,直接自动登录。

添加prompt=select_account参数到登录URL后,虽然可以选择其他账户,但移除该参数后又会自动登录原账户。OAuth回调处理正常,能获取令牌、加载用户信息等,问题仅在于登出未失效联邦会话。


环境变量配置

NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URI=http://localhost:3000

相关代码

登录/登出辅助函数

export const getCognitoAuthUrl = () => {
  const domain = process.env.NEXT_PUBLIC_COGNITO_DOMAIN!;
  const clientId = process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!;
  const redirectUri = process.env.NEXT_PUBLIC_REDIRECT_URI!;

  return `${domain}/login?client_id=${clientId}&response_type=code&scope=email+openid+profile&redirect_uri=${encodeURIComponent(
    redirectUri
  )}&prompt=select_account`;
};

export const signOut = () => {
  const domain = process.env.NEXT_PUBLIC_COGNITO_DOMAIN!;
  const clientId = process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!;
  const logoutUri = process.env.NEXT_PUBLIC_LOGOUT_URI!;
  
  localStorage.removeItem('accessToken');
  localStorage.removeItem('userId');
  localStorage.removeItem('userInfo');
  
  window.location.href = `${domain}/logout?client_id=${clientId}&logout_uri=${encodeURIComponent(logoutUri)}`;
};

Home页面(回调处理)

"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/hooks/useAuth";
import HomeContent from "@/components/home/HomeContent";

export default function Home() {
  const { user, isLoaded } = useAuth();
  const router = useRouter();

  useEffect(() => {
    const handleAuthCallback = async () => {
      const urlParams = new URLSearchParams(window.location.search);
      const code = urlParams.get("code");

      if (code) {
        try {
          const response = await fetch("/api/auth/callback", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ code })
          });

          const data = await response.json();
          if (data.success) {
            localStorage.setItem("accessToken", data.accessToken);
            localStorage.setItem("userId", data.userId);
            localStorage.setItem("userInfo", JSON.stringify(data.userInfo));
            window.history.replaceState({}, document.title, "/");
            window.location.reload(); // Reload to update auth state
          } else {
            router.push("/login");
          }
        } catch (error) {
          console.error("Auth callback error:", error);
          router.push("/login");
        }
      }
    };

    handleAuthCallback();
  }, [router]);

  // Show loading while checking auth
  if (!isLoaded) {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="text-center">
          <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto mb-4"></div>
          <p className="text-gray-600">Loading...</p>
        </div>
      </div>
    );
  }

  return <HomeContent user={user} />;
}

Auth回调API路由

import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  try {
    const { code } = await request.json();
    console.log("Received code:", code);

    if (!code) {
      return NextResponse.json({ success: false, error: "No code provided" });
    }

    // Exchange authorization code for tokens
    const tokenResponse = await fetch(
      `${process.env.NEXT_PUBLIC_COGNITO_DOMAIN}/oauth2/token`,
      {
        method: "POST",
        headers: {
          "Content-Type": "application/x-www-form-urlencoded"
        },
        body: new URLSearchParams({
          grant_type: "authorization_code",
          client_id: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
          client_secret: process.env.COGNITO_CLIENT_SECRET!,
          code,
          redirect_uri: process.env.NEXT_PUBLIC_REDIRECT_URI!
        })
      }
    );

    const tokens = await tokenResponse.json();
    console.log("Token response:", tokens);

    if (!tokens.access_token) {
      console.error("No access token received:", tokens);
      return NextResponse.json({
        success: false,
        error: "Failed to get tokens",
        details: tokens
      });
    }

    // Get user info
    const userResponse = await fetch(
      `${process.env.NEXT_PUBLIC_COGNITO_DOMAIN}/oauth2/userInfo`,
      {
        headers: {
          Authorization: `Bearer ${tokens.access_token}`
        }
      }
    );

    const userInfo = await userResponse.json();
    console.log("User info:", userInfo);

    return NextResponse.json({
      success: true,
      accessToken: tokens.access_token,
      userId: userInfo.sub,
      userInfo
    });
  } catch (error) {
    console.error("Auth callback error:", error);
    return NextResponse.json({
      success: false,
      error: "Authentication failed",
      details: error.message
    });
  }
}

当前现象

  • 应用本地状态已正确清除
  • Cognito托管UI已重定向回应用
  • Google仍存在活跃会话,点击“Sign in with Google”直接返回授权码,不显示Google登录界面
  • 表面登出成功,但Google会话未被撤销

解决方案

要彻底登出并让Google登录对话框重新出现,需让Cognito在登出时同时终止Google的联邦会话,具体步骤如下:

1. 确保Cognito配置支持联邦登出

在AWS控制台的Cognito用户池→身份提供商→Google中,检查:

  • Sign-out URL已设置为你的应用登出回调地址(即NEXT_PUBLIC_LOGOUT_URI)
  • Google Cloud控制台中,OAuth 2.0客户端ID的Authorized redirect URIs包含Cognito的回调地址(格式:https://<你的Cognito域名>/oauth2/idpresponse)

2. 优化登出逻辑

当前的登出URL参数已正确,但需确保Cognito能触发联邦登出流程。无需修改参数,保持现有signOut函数即可,Cognito会自动向Google发送登出请求。

3. 强制登录时显示Google登录界面(备选方案)

如果联邦登出仍有问题,可在登录URL中使用prompt=login参数替代prompt=select_account,强制Google显示登录界面:

return `${domain}/login?client_id=${clientId}&response_type=code&scope=email+openid+profile&redirect_uri=${encodeURIComponent(
  redirectUri
)}&prompt=login`;

关键原理

Cognito的联邦登出依赖身份提供商(Google)的单点登出(SLO)支持,配置正确时,Cognito的/logout端点会自动向Google发送登出请求,清除用户的Google会话。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 01:40:57