Сбой аутентификации Cognito завершается с "Уже найдена запись для имени пользователя Facebook_10155611263153532"

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

Одна важная вещь, которую я хочу удовлетворить, - это объединение учетных записей пользователей пула с одинаковым адресом электронной почты.

Я выполняю это, вызывая adminLinkProviderForUser в рамках триггера лямбды PreSignUp_ExternalProvider cognito.

Так что с этим все работает. Новый предоставленный социальный пользователь регистрируется и связывается с уже существующим пользователем Cognito (user+pass).

Однако процесс аутентификации с точки зрения пользователя не завершается. Сбой на последнем шаге, где вызывается обратный вызов uri (определенный в пуле пользователей cognito):

ошибка: invalid_request

error_description: Уже найдена запись для имени пользователя Facebook_10155611263152353

Но затем, если пользователь повторяет поток социальной аутентификации, все работает и получает токены сеанса, которые представляют исходного пользователя Cognito User Pool (тот, который уже имел это электронное письмо).

Обратите внимание, что я проверяю поток аутентификации на пустом пуле пользователей, ноль учетных записей пользователей.

9 ответов

Для всех бедняков, борющихся с этой проблемой еще в 2020 году, как и я:

  • В конце концов я решил проблему, поймав сообщение "Уже найдена запись для имени пользователя" в моем клиентском приложении и повторив весь процесс аутентификации еще раз.
  • К счастью, ошибка возникает только при первоначальной регистрации внешнего поставщика, но не при последующих входах того же пользователя (потому что это происходит во время триггера регистрации, да). Я делаю безумное предположение, но думаю, что происходит следующее:
    • В моем случае провайдер facebook успешно связался с уже существующим пользователем электронной почты / паролем когнито. новая запись пула пользователей Facebook, связанная с адресом электронной почты / паролем пользователя, была успешно создана.
    • Тем не менее, похоже, что cognito попытался зарегистрировать полностью изолированного пользователя Facebook_id во время внутреннего процесса регистрации (даже если на предыдущем шаге уже была создана ссылка пользователя с тем же именем пользователя). Поскольку "связующий пользователя" с именем пользователя Facebook_id уже существовал, когнито выдало внутреннюю ошибку "Уже найдена запись для ошибки имени пользователя Facebook_id".
    • Эта ошибка неоднократно озвучивалась разработчиками AWS с 2017 года, и есть даже некоторые ответы, в которых они работают над ней, но в 2020 году она все еще не исправлена.

Да, это так, как сейчас. Если вы попытаетесь связать пользователей с помощью триггера PreSignUp, первый раз не будет работать. Лучший способ справиться с этим (я думаю) состоит в том, чтобы предоставить возможность в вашем пользовательском интерфейсе связывать внешние учетные записи при входе в систему. В триггере предварительной регистрации найдите пользователя с таким же уникальным атрибутом (например, адрес электронной почты) и посмотрите, не зарегистрирован ли он у внешнего поставщика. Затем показать сообщение, такое как электронная почта уже существует. Войдите в систему и используйте это меню / опцию для связи. Не проверял это все же.

Эта ошибка внезапно прекратилась 21.02.23? Мы ничего не меняли, но теперь это больше не происходит с пользователями при их первой регистрации. Мы также заметили, что пользовательский интерфейс Cognito для отображения связанных пользователей отличается — в Cognito можно увидеть только одну учетную запись Cognito, а не несколько. Вы по-прежнему можете видеть федеративные связанные учетные записи вidentitiesсобственность, хотя

Я наконец-то заставил эту штуку работать нестандартным образом, когда пользователи должны авторизоваться дважды или что-то еще.

Объяснение процесса:

  1. Пользователь пытается аутентифицироваться с помощью поставщика удостоверений, впервые => PreSignUp lambda запускается и проверяет, существует ли пользователь по электронной почте

1а. Если пользователь существует, он выдаст ошибку, например. CONFIRM_IDENTITY_LINK_токен, который я записываю на клиенте. токен - это строка base64 с именем пользователя и идентификатором личности ("username:facebook_123456")

1b. Если имя пользователя не существует, я создаю нового пользователя с временным паролем и выдаю ошибку FORCE_CHANGE_PASSWORD_token. Тот же токен, но на этот раз я добавляю временный пароль.

  1. В клиенте у меня есть один маршрут обратного вызова '/authorize' => это тот, который вы настроили как URL-адрес обратного вызова в Cognito, и 2 дополнительных маршрута: '/ confirm-password' и '/ configure-password'.

В маршруте / authorize я фиксирую ошибки и получаю прикрепленные токены и перенаправляю на дополнительные маршруты: 1a => /configure-password? Token = token и 1b => / confirm-password? Token=token

Для "/confirm-password" я прошу пользователя подтвердить свой текущий пароль, чтобы разрешить связывание с провайдером, а затем использовать токен для входа в систему с идентификатором личности в качестве clientMetadata, например, "{"LINK_PROVIDER":"facebbok_12345678"}"

При входе в систему у меня есть лямбда PostAuthentication, которая проверяет наличие «LINK_PROVIDER» в clientMetadata и связывает его с пользователем.

Для «/ configure-password» я анализирую токен и выполняю «неглубокий» вход с учетными данными из токена и идентификатора идентификатора в качестве метаданных клиента (как указано выше), а затем предлагаю пользователю настроить новый пароль для своей учетной записи.

Я знаю, что это может показаться немного ограничительным, но я считаю, что это лучше, чем авторизовывать дважды.

Кроме того, это не создает дополнительных пользователей для удостоверений в пользовательском пуле.

Примеры кода:

PreSignUp лямбда

      export async function handler(event: PreSignUpTriggerEvent) {
  try {
    const { userPoolId, triggerSource, request, userName } = event
    if (triggerSource === 'PreSignUp_ExternalProvider') {
      // Check if user exists in cognito
      let currentUser = await getUserByEmail(userPoolId, request.userAttributes.email)

      if (currentUser) {
        // User exists, thow error with identity id
        const identity = Buffer.from(`${currentUser}:${userName}`).toString('base64')
        throw new Error(`CONFIRM_USER_IDENTITY_${identity}`)
      }

      // Create new Cognito user with temp password
      const tempPassword = generatePassword()
      currentUser = await createNewUser(userPoolId, request.userAttributes, tempPassword)
      // Throw error with token
      const state = Buffer.from(`${currentUser}:${tempPassword}:${userName}`).toString('base64')
      throw new Error(`FORCE_CHANGE_PASSWORD_${state}`)
    }
    return event
  } catch (error) {
    throw new Error(error)
  }
}

Лямбда после аутентификации

      export async function handler(event: PostAuthenticationTriggerEvent) {
  try {
    const { userPoolId, request, userName } = event

    if (request.clientMetadata?.LINK_IDENTITY) {
      const identity = request.clientMetadata['LINK_IDENTITY']
      // Link identity to user
      await linkIdentityProvider(userPoolId, userName, identity)
    }

    return event
  } catch (error) {
    console.error(error)
    throw new Error('Internal server error')
  }
}

Мы столкнулись с той же проблемой и попробовали различные хаки, чтобы обойти ее. Когда мы начали использовать SignInWithApple, мы не могли справиться с «двойным поворотом», потому что Apple всегда хочет, чтобы пользователь вводил свою электронную почту и пароль, а не Google, где во второй раз все работает автоматически. Таким образом, решение, которое мы в итоге создали, состояло в том, чтобы сохранить идентификатор Cognito/IdP (Google_1234, SignInWithApple_XXXX.XXX.XXX) в нашей базе данных, но при этом создать собственного пользователя Cognito, который не связан через Cognito. Собственный пользователь создается для упрощения отмены связи, поскольку сначала мы избавляемся от данных (идентификатор пользователя IdP), которые мы храним в нашей базе данных, а затем от пользователя Cognito IdP. Затем пользователь может продолжить работу с пользователем Native Cognito. Затем у нас есть компонент промежуточного программного обеспечения, который позволяет нам иметь JWT во внешнем формате IdP или собственном формате Cognito и переводит, чтобы мы могли использовать обе версии. Пока пользователь использует IdP/SSO, мы сбрасываем пароль собственных пользователей на очень длинное случайное значение и предотвращаем его сброс, поэтому они должны использовать IdP.

Итак, что бы вы ни пытались сделать, не используйте admin-link-provider-for-userкоманда!

Чтобы уточнить ответ @agent420, это то, что я сейчас использую (пример Typescript).

Когда социальная личность пытается зарегистрироваться, а адрес электронной почты уже существует, я ловлю это с помощью PreSignUpтриггер, а затем вернуть пользователю сообщение об ошибке. Внутри приложения на странице профиля пользователя есть возможность связать поставщика удостоверений, который вызываетadminLinkProviderForUser API.

import {
    Context,
    CognitoUserPoolTriggerEvent,
    CognitoUserPoolTriggerHandler,
} from 'aws-lambda';
import * as aws from 'aws-sdk';
import { noTryAsync } from 'no-try';

export const handle: CognitoUserPoolTriggerHandler = async (
    event: CognitoUserPoolTriggerEvent,
    context: Context,
    callback: (err, event: CognitoUserPoolTriggerEvent) => void,
): Promise<any> => {
    context.callbackWaitsForEmptyEventLoop = false;

    const { email } = event.request.userAttributes;

    // pre sign up with external provider
    if (event.triggerSource === 'PreSignUp_ExternalProvider') {
        // check if a user with the email address already exists

        const sp = new aws.CognitoIdentityServiceProvider();

        const { error } = await noTryAsync(() =>
            sp
                .adminGetUser({
                    UserPoolId: 'your-user-pool-id',
                    Username: email,
                })
                .promise(),
        );

        if (error && !(error instanceof aws.AWSError)) {
            throw error;
        } else if (error instanceof aws.AWSError && error.code !== 'UserNotFoundException') {
            throw error;
        }
    }

    callback(null, event);
};

Тот же код в JavaScript был вызван getUser вместо listUsers. Также предполагается, что все пользователи имеют свой электронный идентификатор в качестве имени пользователя.

const aws = require('aws-sdk');

exports.handler = async (event, context, callback) => {
    console.log("event" + JSON.stringify(event));
    const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});
    const emailId = event.request.userAttributes.email
    const userName = event.userName
    const userPoolId = event.userPoolId
    var params = {
        UserPoolId: userPoolId,
        Username: userName
    };
    var createUserParams = {
        UserPoolId: userPoolId,
        Username: emailId,
        UserAttributes: [
            {
                Name: "email",
                Value: emailId
            },
        ],
        TemporaryPassword: "xxxxxxxxx"
    };

    var googleUserNameSplitArr = userName.split("_");
    var adminLinkUserParams = {
        DestinationUser: {
            ProviderAttributeName: 'UserName',
            ProviderAttributeValue: emailId,
            ProviderName: "Cognito"
        },
        SourceUser: {
            ProviderAttributeName: "Cognito_Subject",
            ProviderAttributeValue: googleUserNameSplitArr[1],
            ProviderName: 'Google'
        },
        UserPoolId: userPoolId
    };

    var addUserToGroupParams = {
        GroupName: "Student",
        UserPoolId: userPoolId,
        Username: emailId
    };

    if (userName.startsWith("Google_")) {
        await cognitoidentityserviceprovider.adminGetUser(params, function (err, data) {
            if (err) {
                console.log("No user present")
                console.log(err, err.stack);
                cognitoidentityserviceprovider.adminCreateUser(createUserParams, function (err, data) {
                    if (err) console.log(err, err.stack);
                    else {
                        console.log("User Created ")
                        cognitoidentityserviceprovider.adminAddUserToGroup(addUserToGroupParams, function (err, data) {
                            if (err) console.log(err, err.stack);
                            else {

                                console.log("added user to group");
                                console.log(data);
                            }
                        });
                        cognitoidentityserviceprovider.adminLinkProviderForUser(adminLinkUserParams, function (err, data) {
                            if (err) console.log(err, err.stack);
                            else {
                                console.log("user linked");
                                console.log(data);
                            }
                        });
                        console.log(data);
                    }
                });
            } else {
                console.log("user already present")
                cognitoidentityserviceprovider.adminLinkProviderForUser(adminLinkUserParams, function (err, data) {
                    if (err) console.log(err, err.stack); // an error occurred
                    else {
                        console.log("userlinked since user already existed");
                        console.log(data);
                    }
                });
                console.log(data);
            }
        });
    }
    console.log("after the function custom");
    callback(null, event);
};

Это хорошо известная ошибка. Я обрабатываю это, повторяя запрос после этой ошибки, и он будет работать. Ошибка связана с тем, что в SDK нет способа сообщить пулу, что вы уже связываете учетные данные федерации с пользователем, и он пытается создать нового пользователя с этими учетными данными.

Я хотел, чтобы пользователь мог беспрепятственно входить в систему с помощью одного социального провайдера (например, Facebook), а затем другого (Google). Я боролся с процессом повторной попытки, особенно с входом в Google. В процессе регистрации, если у пользователя несколько учетных записей, ему нужно будет дважды обработать выбор учетной записи.
В итоге я просто использовал Cognito для кода на стороне клиента и генерации токенов, а также имел лямбду в процессе предварительной регистрации, сопоставляющую идентификаторы пользователей с их электронной почтой в пользовательской БД (Postgres или DynamoDB). Затем, когда пользователь запрашивает мой API на основе своего идентификатора пользователя (будь то FacebookId или идентификатор пользователя электронной почты cognito, я запрашиваю БД, чтобы найти связанное электронное письмо, и я могу аутентифицировать любых пользователей и их данные, подобные этому.

Другие вопросы по тегам