I’m using AWS Cognito with Google as an Identity Provider in a Next.js app. I am using AWS managed login as well. Sign-in works correctly, tokens are received, and the user session persists. However, when I call the sign-out URL, the browser redirects but the user is not fully logged out. When I click "Sign in with Google" again, it appears Google still thinks I'm authenticated and was never logged out.
Adding the param prompt=select_account to the login url allows me to choose another account and the logout appears to work but when I remove that from the string I am automatically signed back into the previous account why? Here is my sign-in and sign-out helper code:
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)}`;
};
"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} />;
}
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
});
}
}
The OAuth callback handling works fine, tokens are received, user info loads, etc. The issue is specifically logout not invalidating the federated session.
My redirect environment variables:
NEXT_PUBLIC_REDIRECT_URI=http://localhost:3000
NEXT_PUBLIC_LOGOUT_URI=http://localhost:3000
What is happening:
Question: How do I properly log the user out so the Google login dialog appears again, rather than Cognito silently re-authenticating with the existing Google session?
Do I need to change the logout_uri, or explicitly revoke the Google session as well?
Any guidance would be appreciated.
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)}&federated=true`;
};
After much struggle I was able to fix it with the following code. Adding the federated=true param to the query string of the logout uri.