41

目标是实现用户池应用程序集成和联合中所述的社交提供者身份验证流程。

我想要满足的一件重要事情是合并具有相同电子邮件地址的用户池帐户。

我通过在 PreSignUp_ExternalProvider cognito lambda 触发器中调用adminLinkProviderForUser来实现这一点。

所以有了这个,一切正常。新的社交提供用户正在注册并与现有的 Cognito(用户+通行证)用户链接。

但是,从用户的角度来看,身份验证流程并未完成。它在调用回调 uri(在 cognito 用户池中定义)的最后一步失败:

错误:invalid_request

error_description:已找到用户名 Facebook_10155611263152353 的条目

但是,如果用户重试社交身份验证流程,一切正常,并且会获得代表原始 Cognito 用户池用户(已经拥有该电子邮件的用户)的会话令牌。

请注意,我正在空用户池、零用户帐户上测试身份验证流程。

4

7 回答 7

24

对于仍然在 2020 年与这个问题作斗争的所有可怜的灵魂,就像我一样:

  • 我最终通过在我的客户端应用程序中捕获“已找到用户名条目”并再次重复整个身份验证流程来解决此问题。
  • 幸运的是,该错误仅在初始外部提供商注册时触发,但不会在同一用户的后续登录中触发(因为它发生在注册触发期间,呵呵)。我在猜测,但这是我认为正在发生的事情:
    • 就我而言,Facebook 提供商成功地与预先存在的 cognito 电子邮件/密码用户相关联。链接到电子邮件/密码用户的新 Facebook 用户池条目已成功创建。
    • 尽管如此,似乎 cognito 试图在内部注册过程中注册完全隔离的 Facebook_id 用户(即使在上一步中已经创建了具有相同用户名的链接用户条目)。由于用户名 Facebook_id 的“链接用户”已经存在,因此 cognito 抛出“已经找到用户名 Facebook_id 错误的条目”内部错误。
    • 自 2017 年以来,AWS 开发人员已多次向 AWS 开发人员提出此错误,甚至有一些他们正在解决此问题的回应,但在 2020 年,它仍未修复。
于 2020-03-13T15:32:40.647 回答
5

是的,这就是它当前的设置方式。如果您尝试使用 PreSignUp 触发器链接用户,第一次将不起作用。处理此问题的更好方法(我认为)是在您的 UI 中提供一个选项,以在登录时链接外部帐户。在预注册触发器中,搜索具有相同唯一属性(例如电子邮件)的用户,并查看注册是否来自外部提供商。然后显示一条消息,例如电子邮件已存在。登录并使用此菜单/选项进行链接。虽然没有测试过这个。

于 2017-12-18T10:48:16.243 回答
1

我终于让这件事以一种不奇怪的方式工作,用户必须授权两次或其他事情。

流程说明:

  1. 用户第一次尝试使用身份提供者进行身份验证 => PreSignUp lambda 启动并通过电子邮件检查用户是否存在

1a。如果用户存在,它将抛出一个错误,例如。我在客户端上捕获的 CONFIRM_IDENTITY_LINK_令牌。令牌是带有用户名和身份 ID 的 base64 字符串(“用户名:facebook_123456”)

1b。如果用户名不存在,我会使用临时密码创建一个新用户并抛出错误 FORCE_CHANGE_PASSWORD_ token。相同的令牌,但我这次添加了临时密码。

  1. 在客户端中,我有一个回调路由“/authorize”=> 这是您在 Cognito 中设置为回调 URL 的路由,还有 2 个额外的路由:“/confirm-password”和“/configure-password”。

在 /authorize 路由中,我正在捕获错误并获取附加的令牌并重定向到额外的路由: 1a => /configure-password?token= token和 1b => /confirm-password?token= token

对于“/confirm-password”,我要求用户确认其当前密码以授权与提供者的链接,然后使用令牌以身份 ID 作为 clientMetadata 登录他,例如“{”LINK_PROVIDER”:“facebbok_12345678” }"

登录时,我有一个 PostAuthentication lambda,它检查 clientMetadata 中的“LINK_PROVIDER”,并将其链接到用户。

对于“/configure-password”,我解析令牌并使用来自令牌的凭据和身份 ID 作为客户端元数据(与上面相同)进行“浅”登录,然后提示用户为其帐户配置新密码。

我知道这似乎有点限制,但我发现它比授权两次要好。

此外,这不会为用户池中的身份创建额外的用户。

代码示例:

PreSignUp lambda

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

PostAuthentication lambda

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')
  }
}
于 2021-10-30T11:46:12.057 回答
1

为了详细说明@agent420 的答案,这就是我目前正在使用的(Typescript 示例)。

当社交身份尝试注册并且电子邮件地址已经存在时,我会使用PreSignUp触发器捕捉到这一点,然后将错误消息返回给用户。在应用程序内部,在用户的个人资料页面上,有一个选项可以链接调用adminLinkProviderForUserAPI 的身份提供者。

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

于 2019-12-22T11:45:47.187 回答
0

这是一个众所周知的错误。我通过在此错误后重试请求来处理它,它会起作用。该错误是因为 SDK 中没有办法让池知道您已经将联合凭据链接到用户,并且它尝试使用这些凭据创建新用户

于 2021-10-30T23:54:07.457 回答
0

我希望拥有让用户能够无缝登录一个社交提供商(例如:Facebook)和另一个(谷歌)的功能。我在重试过程中遇到了困难,尤其是在使用 Google 登录时。在注册过程中,如果用户有多个帐户,他将需要处理两次帐户选择。
我最终做的只是将 Cognito 用于客户端代码和令牌生成,并在预注册过程中使用 lambda,将用户 ID 与他们的电子邮件映射到自定义数据库(Postgres 或 DynamoDB)中。然后,当用户根据他们的 userId(无论是 FacebookId 还是 cognito 电子邮件 userId 查询我的 API 时,我正在查询数据库以找到链接的电子邮件,并且我能够像这样对任何用户及其数据进行身份验证。

于 2022-02-15T13:02:21.133 回答
0

JavaScript getUser 中的相同代码已被调用,而不是 listUsers。还假设所有用户都有他们的电子邮件 ID 作为他们的用户名。

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);
};
于 2020-09-10T12:54:17.497 回答