The goal is to implement a social provider auth flow as described in User Pools App Integration and Federation.
One important thing that I want to satisfy, is to merg
To elaborate on @agent420's answer, this is what I am currently using (Typescript example).
When a social identity attempts to sign up and the email address already exists I catch this using the PreSignUp
trigger and then return an error message to the user. Inside the app, on the user's profile page, there is an option to link an identity provider which calls the 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);
};
The same code in JavaScript getUser has been called instead of listUsers. It is also assumed that all users have their email id as their username.
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);
};
For all the poor souls fighting with this issue still in 2020 the same way I did:
Yes, this is how it is currently setup. If you try to link users using PreSignUp trigger, the first time won't work. A better way to handle this(I think) would be to provide an option in your UI to link external accounts on sign-in. In the pre-signup trigger, search for a user with the same unique attribute (say email) and see if the sign up is from external provider. Then show a message such as email already exists. Login in & use this menu/option to link. Haven't tested this though.