Другое

Исправление проблемы выхода из AWS Cognito с Google в Next.js

Полное руководство по исправлению проблем выхода из AWS Cognito с Google в Next.js. Узнайте, как правильно аннулировать федеративные сеансы и реализовать полный процесс выхода.

Проблема с выходом из системы AWS Cognito Google Sign-in в Next.js

Я реализую AWS Cognito с Google в качестве поставщика идентификации в приложении Next.js. Хотя функционал входа работает правильно и токены корректно получаются, процесс выхода из системы не полностью завершает выход пользователя.

Описание проблемы

Когда я вызываю URL для выхода из системы, браузер перенаправляет, но пользователь не полностью выходит из системы. При повторном нажатии “Войти с Google” Google все еще имеет активную сессию, обходя экран входа и автоматически возвращая код авторизации.

Текущая реализация

Я использую следующий код для аутентификации:

Вспомогательная функция входа

typescript
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`;
};

Вспомогательная функция выхода

typescript
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)}`;
};

Обработчик обратного вызова аутентификации

typescript
"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();
          } else {
            router.push("/login");
          }
        } catch (error) {
          console.error("Auth callback error:", error);
          router.push("/login");
        }
      }
    };

    handleAuthCallback();
  }, [router]);

  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} />;
}

API обратного вызова аутентификации

typescript
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" });
    }

    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
      });
    }

    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
    });
  }
}

Переменные окружения

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

Что я пробовал

Добавление prompt=select_account в URL входа позволяет выбрать другую учетную запись, и выход из системы кажется работает. Однако, когда я удаляю этот параметр, я автоматически снова вхожу в предыдущую учетную запись.

Конкретные вопросы

  1. Как правильно выйти из систему, чтобы снова появилось диалоговое окно входа в Google, а не чтобы Cognito тихо повторно аутентифицировался с существующей сессией Google?
  2. Нужно ли изменить параметр logout_uri?
  3. Нужно ли явно отзывать сессию Google?

Буду признателен за любые рекомендации по реализации полного процесса выхода из системы с AWS Cognito и Google в качестве поставщика идентификации в Next.js.

Содержание


Понимание проблемы выхода из системы

Основная проблема заключается в том, что процесс выхода из AWS Cognito не автоматически аннулирует сеанс поставщика идентификации Google при использовании стандартного потока выхода из системы. Как сообщают пользователи Stack Overflow, хотя обработка OAuth-колбэка работает корректно и токены получаются нормально, “проблема заключается именно в том, что выход из системы не аннулирует федеративный сеанс.”

Это означает:

  • Локальное состояние вашего приложения (localStorage, состояние сессии) корректно очищается
  • Cognito hosted UI перенаправляет обратно в ваше приложение
  • Однако Google все еще поддерживает активный сеанс
  • Когда пользователь пытается войти снова, Google автоматически предоставляет код авторизации без отображения экрана входа

Параметр prompt=select_account работает как обходной путь, поскольку он заставляет Google отображать диалог выбора аккаунта, но это не должно быть постоянным решением для производственного приложения.


Правильная реализация выхода из системы

Ваша текущая функция выхода из системы имеет несколько проблем, которые предотвращают полный выход:

Проблемы текущей реализации

typescript
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)}`;
};

Отсутствующие элементы:

  1. Очищает только localStorage, но игнорирует sessionStorage и cookies
  2. Не обрабатывает хранилище на уровне браузера, которое может использовать Cognito
  3. Отсутствуют важные параметры выхода для федеративных провайдеров
  4. Нет специальной обработки сеанса Google

Улучшенная функция выхода из системы

typescript
export const signOut = async () => {
  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
  localStorage.removeItem('accessToken');
  localStorage.removeItem('userId');
  localStorage.removeItem('userInfo');
  
  // Очистить sessionStorage
  sessionStorage.clear();
  
  // Очистить все связанные с Cognito cookies
  document.cookie.split(';').forEach(cookie => {
    const name = cookie.split('=')[0].trim();
    if (name.includes('amazon') || name.includes('cognito') || name.includes('google')) {
      document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
    }
  });
  
  // Собрать правильный URL выхода с дополнительными параметрами
  const logoutUrl = new URL(`${domain}/logout`);
  logoutUrl.searchParams.append('client_id', clientId);
  logoutUrl.searchParams.append('logout_uri', logoutUri);
  logoutUrl.searchParams.append('redirect_uri', logoutUri);
  
  // Добавить параметр token_hint, если у вас есть доступ к ID токену
  const idToken = localStorage.getItem('idToken');
  if (idToken) {
    logoutUrl.searchParams.append('token_hint', idToken);
  }
  
  // Выполнить выход и перенаправление
  try {
    await fetch(logoutUrl.toString(), {
      method: 'GET',
      credentials: 'include'
    });
  } catch (error) {
    console.error('Запрос на выход не выполнен:', error);
  }
  
  // Финальное перенаправление на URI выхода
  window.location.href = logoutUrl.toString();
};

Недействительность сеанса Google

Чтобы правильно аннулировать сеанс Google, необходимо включить дополнительные параметры и, возможно, сделать прямой вызов точки выхода Google. Согласно исследованиям, “Next-auth-example с Cognito не вызывает URL выхода Cognito при выходе из системы, оставляя пользователя вошедшим в Cognito.”

Улучшенный выход с обработкой сеанса Google

typescript
export const signOut = async () => {
  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!;
  
  // Очистить все хранилища
  clearAllStorage();
  
  try {
    // Шаг 1: Выйти из Cognito с правильными параметрами
    await cognitoLogout(domain, clientId, logoutUri);
    
    // Шаг 2: Попытаться выйти из Google напрямую
    await googleLogout();
    
    // Шаг 3: Перенаправить на URI выхода
    window.location.href = logoutUri;
  } catch (error) {
    console.error('Процесс выхода не выполнен:', error);
    // Резервный вариант перенаправления, даже если некоторые шаги не выполнены
    window.location.href = logoutUri;
  }
};

const clearAllStorage = () => {
  // Очистить localStorage
  localStorage.removeItem('accessToken');
  localStorage.removeItem('userId');
  localStorage.removeItem('userInfo');
  localStorage.removeItem('idToken');
  
  // Очистить sessionStorage
  sessionStorage.clear();
  
  // Очистить cookies
  clearAuthCookies();
};

const clearAuthCookies = () => {
  const cookies = document.cookie.split(';');
  cookies.forEach(cookie => {
    const [name] = cookie.split('=').map(s => s.trim());
    const authRelatedCookies = [
      'amplify-id-token',
      'amplify-session-token',
      'amplify-aws cognito identity id',
      'google-auth-session'
    ];
    
    if (authRelatedCookies.some(authCookie => name.includes(authCookie))) {
      document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${window.location.hostname};`;
    }
  });
};

const cognitoLogout = async (domain: string, clientId: string, logoutUri: string) => {
  const logoutUrl = new URL(`${domain}/logout`);
  logoutUrl.searchParams.append('client_id', clientId);
  logoutUrl.searchParams.append('logout_uri', logoutUri);
  logoutUrl.searchParams.append('redirect_uri', logoutUri);
  
  // Добавить token_hint, если доступен
  const idToken = localStorage.getItem('idToken');
  if (idToken) {
    logoutUrl.searchParams.append('token_hint', idToken);
  }
  
  const response = await fetch(logoutUrl.toString(), {
    method: 'GET',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  });
  
  if (!response.ok) {
    throw new Error(`Выход из Cognito не выполнен: ${response.statusText}`);
  }
  
  return response;
};

const googleLogout = async () => {
  // Google не предоставляет универсальной точки выхода, но вы можете попробовать
  // отозвать токен или перенаправить на страницу выхода Google
  try {
    // Если у вас есть access token, вы можете попробовать его отозвать
    const accessToken = localStorage.getItem('accessToken');
    if (accessToken) {
      await fetch('https://accounts.google.com/o/oauth2/revoke', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: `token=${accessToken}`
      });
    }
    
    // Альтернатива: Перенаправить на страницу выхода Google
    const googleLogoutUrl = 'https://accounts.google.com/Logout';
    window.open(googleLogoutUrl, '_blank', 'width=1,height=1');
  } catch (error) {
    console.warn('Выход из Google не выполнен:', error);
    // Это не критично, так как основной выход все еще должен работать
  }
};

Требования к конфигурации Cognito

Для правильной работы функции выхода из системы ваша конфигурация AWS Cognito должна быть настроена правильно. Согласно документации AWS, вам необходимо убедиться в следующем:

1. Конфигурация поставщика идентификации

Убедитесь, что ваш поставщик идентификации Google в Cognito имеет:

  • Client ID и Client Secret настроены правильно
  • Scopes включают openid, profile и email
  • Authorization endpoint и Logout endpoint установлены правильно

2. Конфигурация приложения клиента

В настройках приложения пула пользователей Cognito:

  • Включите “Enable identity providers” для Google
  • Установите Callback URL и Sign-out URL на домены вашего приложения
  • Включите “Allow OAuth implicit flow”, если используете этот паттерн
  • Настройте Allowed OAuth flows (обычно code для потока кода авторизации)

3. Конфигурация домена

Убедитесь, что ваш домен Cognito настроен правильно с:

  • Domain prefix установлен
  • Custom domain, если используется
  • SSL certificate правильно установлен

4. Проверка URL выхода

Убедитесь, что ваш NEXT_PUBLIC_LOGOUT_URI:

  • Добавлен в “Allowed callback URLs” в Cognito
  • Добавлен в “Allowed sign-out URLs” в Cognito
  • Правильно закодирован при использовании в URL

Полное решение для выхода из системы

Вот комплексное решение, которое охватывает все аспекты проблемы выхода из системы:

Улучшенный компонент выхода из системы

typescript
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { signOut } from "@/lib/auth";

export default function LogoutPage() {
  const router = useRouter();

  useEffect(() => {
    const performLogout = async () => {
      try {
        await signOut();
        // Функция signOut обрабатывает перенаправление
      } catch (error) {
        console.error("Выход не выполнен:", error);
        // Резервное перенаправление
        router.push("/login");
      }
    };

    performLogout();
  }, [router]);

  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">Выход из системы...</p>
      </div>
    </div>
  );
}

Полная библиотека аутентификации

typescript
// lib/auth.ts

export interface SignOutOptions {
  clearCookies?: boolean;
  revokeGoogleToken?: boolean;
  redirectUri?: string;
}

export const signOut = async (options: SignOutOptions = {}) => {
  const {
    clearCookies = true,
    revokeGoogleToken = true,
    redirectUri = process.env.NEXT_PUBLIC_LOGOUT_URI || '/'
  } = options;

  const domain = process.env.NEXT_PUBLIC_COGNITO_DOMAIN!;
  const clientId = process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!;

  // Очистить все хранилища
  clearAllStorage();
  
  try {
    // Выполнить выход из Cognito
    await cognitoLogout(domain, clientId, redirectUri);
    
    // При необходимости отозвать токен Google
    if (revokeGoogleToken) {
      await revokeGoogleAccessToken();
    }
    
    // При необходимости очистить cookies
    if (clearCookies) {
      clearAuthCookies();
    }
    
    // Перенаправить на URI выхода
    window.location.href = redirectUri;
    
  } catch (error) {
    console.error('Процесс выхода не выполнен:', error);
    // Все равно перенаправить, даже если некоторые шаги не выполнены
    window.location.href = redirectUri;
  }
};

const clearAllStorage = () => {
  const storageKeys = [
    'accessToken',
    'idToken',
    'refreshToken',
    'userId',
    'userInfo',
    'cognito-token',
    'google-auth-state'
  ];
  
  // Очистить localStorage
  storageKeys.forEach(key => localStorage.removeItem(key));
  
  // Очистить sessionStorage
  sessionStorage.clear();
};

const cognitoLogout = async (domain: string, clientId: string, redirectUri: string) => {
  const logoutUrl = new URL(`${domain}/oauth2/logout`);
  logoutUrl.searchParams.append('client_id', clientId);
  logoutUrl.searchParams.append('logout_uri', redirectUri);
  
  // Получить ID токен, если доступен для token_hint
  const idToken = localStorage.getItem('idToken');
  if (idToken) {
    logoutUrl.searchParams.append('token_hint', idToken);
  }
  
  const response = await fetch(logoutUrl.toString(), {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    }
  });
  
  if (!response.ok) {
    // Попробовать альтернативную точку выхода, если POST не сработал
    const alternativeLogoutUrl = new URL(`${domain}/logout`);
    alternativeLogoutUrl.searchParams.append('client_id', clientId);
    alternativeLogoutUrl.searchParams.append('logout_uri', redirectUri);
    
    const altResponse = await fetch(alternativeLogoutUrl.toString(), {
      method: 'GET',
      credentials: 'include'
    });
    
    if (!altResponse.ok) {
      throw new Error(`Обе точки выхода не сработали`);
    }
  }
};

const revokeGoogleAccessToken = async () => {
  try {
    const accessToken = localStorage.getItem('accessToken');
    if (accessToken) {
      await fetch('https://accounts.google.com/o/oauth2/revoke', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: `token=${accessToken}`
      });
    }
  } catch (error) {
    console.warn('Отзыв токена Google не выполнен:', error);
    // Это не критично
  }
};

const clearAuthCookies = () => {
  const cookies = document.cookie.split(';');
  const authCookiePrefixes = [
    'amplify',
    'cognito',
    'aws',
    'google',
    'oauth'
  ];
  
  cookies.forEach(cookie => {
    const [name] = cookie.split('=').map(s => s.trim());
    
    if (authCookiePrefixes.some(prefix => name.toLowerCase().includes(prefix))) {
      const domain = window.location.hostname;
      document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; domain=${domain};`;
    }
  });
};

// Вспомогательная функция для получения правильного URL входа с контролем prompt
export const getSignInUrl = (forceAccountSelection = false) => {
  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!;
  
  const params = new URLSearchParams({
    client_id: clientId,
    response_type: 'code',
    scope: 'email openid profile',
    redirect_uri: redirectUri,
    identity_provider: 'Google'
  });
  
  if (forceAccountSelection) {
    params.append('prompt', 'select_account');
  }
  
  return `${domain}/login?${params.toString()}`;
};

Альтернативные подходы

Если приведенное выше решение не работает полностью, рассмотрите эти альтернативные подходы:

1. Использование NextAuth.js

Многие разработчики нашли успех при использовании NextAuth.js как абстракционного слоя:

typescript
// nextauth.config.ts
import { CognitoProvider } from "next-auth/providers/cognito";

export const authOptions = {
  providers: [
    CognitoProvider({
      clientId: process.env.NEXT_PUBLIC_COGNITO_CLIENT_ID!,
      clientSecret: process.env.COGNITO_CLIENT_SECRET!,
      issuer: process.env.NEXT_PUBLIC_COGNITO_DOMAIN!,
      authorization: {
        params: {
          prompt: "login", // Принудительный выбор аккаунта
          scope: "openid profile email"
        }
      }
    })
  ],
  callbacks: {
    async jwt({ token, account }) {
      if (account) {
        token.accessToken = account.access_token;
      }
      return token;
    },
    async session({ session, token }) {
      session.accessToken = token.accessToken;
      return session;
    }
  },
  events: {
    signOut: async () => {
      // Пользовательская обработка выхода
    }
  }
};

2. Интеграция AWS Amplify

Использование AWS Amplify может упростить поток аутентификации:

typescript
import { Auth } from 'aws-amplify';

export const amplifySignOut = async () => {
  try {
    // Сначала очистить все хранилища
    localStorage.clear();
    sessionStorage.clear();
    
    // Выйти из Amplify
    await Auth.signOut({ global: true });
    
    // Перенаправить на страницу входа
    window.location.href = '/login';
  } catch (error) {
    console.error('Выход из Amplify не выполнен:', error);
    window.location.href = '/login';
  }
};

3. Ручной отзыв токена

Для полной недействительности сеанса Google:

typescript
export const completeGoogleLogout = async () => {
  try {
    // Получить access token
    const accessToken = localStorage.getItem('accessToken');
    
    if (accessToken) {
      // Отозвать токен Google
      await fetch('https://accounts.google.com/o/oauth2/revoke', {
        method: 'GET',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: `token=${accessToken}`
      });
      
      // Очистить cookies Google
      document.cookie = 'CONSENT=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
      document.cookie = 'SAPISID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
      document.cookie = 'APISID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
      document.cookie = 'HSID=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    }
    
    // Открыть страницу выхода Google в фоновом режиме
    window.open('https://accounts.google.com/Logout', '_blank', 'width=1,height=1');
    
  } catch (error) {
    console.warn('Выход из Google не выполнен:', error);
  }
};

Заключение

Проблема выхода из AWS Cognito Google в Next.js возникает из-за неполной недействительности федеративного сеанса. Вот ключевые выводы:

  1. Полный выход требует нескольких шагов: Очистка localStorage, обработка потока выхода Cognito и аннулирование сеанса Google
  2. Правильное построение URL имеет решающее значение: Включите token_hint, logout_uri и другие необходимые параметры
  3. Сохранение сеанса Google - основная проблема: Google поддерживает активные сеансы, которые обходят экран входа
  4. Конфигурация имеет значение: Убедитесь, что ваш клиент приложения Cognito имеет правильно настроенные callback и logout URL
  5. Сработают несколько подходов: Прямая реализация, абстракция NextAuth.js или интеграция AWS Amplify

Наиболее надежное решение включает реализацию комплексного потока выхода из системы, который обрабатывает все три аспекта: очистку состояния приложения, недействительность сеанса Cognito и прекращение сеанса Google. Начните с улучшенной функции выхода из системы, предоставленной выше, и если проблемы сохранятся, рассмотрите возможность реализации одного из альтернативных подходов с использованием NextAuth.js или AWS Amplify.

Для производственных приложений рекомендуется реализовать полное решение с обработкой ошибок и резервными вариантами, чтобы пользователи всегда могли успешно выйти из системы, независимо от сбоев отдельных компонентов.

Источники

  1. AWS Cognito Google Sign-in with Next.js works, but Sign-out does not fully log the user out - Stack Overflow
  2. Signout does not invoke provider logout url (Cognito) · Issue #5862 · nextauthjs/next-auth
  3. How to completely destroy client side session with NextAuth.js & AWS Cognito - Stack Overflow
  4. How to Build Next.js Apps with Authentication using AWS Amplify and Auth0 | AWS Blogs
  5. NextAuth Logout Not “Working” - Reddit
  6. How to logout from amazon cognito javascript and clear cached identityId - Stack Overflow
Авторы
Проверено модерацией
Модерация