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

