In aws-sdk-js-v3
I'm using @subash approach. I find that when you make an error callback, no extra user is created. Just the one that you create with your email.
const {
CognitoIdentityProviderClient,
ListUsersCommand,
AdminCreateUserCommand,
AdminLinkProviderForUserCommand,
AdminSetUserPasswordCommand,
} = require('@aws-sdk/client-cognito-identity-provider')
const client = new CognitoIdentityProviderClient({
region: process.env.REGION,
})
const crypto = require("crypto")
exports.handler = async(event, context, callback) => {
try {
const {
triggerSource,
userPoolId,
userName,
request: {
userAttributes: { email, name }
}
} = event
if (triggerSource === 'PreSignUp_ExternalProvider') {
const listParam = {
UserPoolId: userPoolId,
Filter: `email = "${email}"`,
}
const listData = await client.send(new ListUsersCommand(listParam))
let [providerName, providerUserId] = userName.split('_')
providerName = providerName.charAt(0).toUpperCase() + providerName.slice(1)
let linkParam = {
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerUserId,
ProviderName: providerName,
},
UserPoolId: userPoolId,
}
//check whether the email already exist, if exist, simply link it, if not create the user first then link.
if (listData && listData.Users.length > 0) {
linkParam['DestinationUser'] = {
ProviderAttributeValue: listData.Users[0].Username,
ProviderName: 'Cognito',
}
}
else {
const createParam = {
UserPoolId: userPoolId,
Username: email,
MessageAction: 'SUPPRESS',
UserAttributes: [{
//optional name attribute.
Name: 'name',
Value: name,
}, {
Name: 'email',
Value: email,
}, {
Name: 'email_verified',
Value: 'true',
}],
}
const createData = await client.send(new AdminCreateUserCommand(createParam))
const pwParam = {
UserPoolId: userPoolId,
Username: createData.User.Username,
Password: crypto.randomBytes(40).toString('hex'),
Permanent: true,
}
await client.send(new AdminSetUserPasswordCommand(pwParam))
linkParam['DestinationUser'] = {
ProviderAttributeValue: createData.User.Username,
ProviderName: 'Cognito',
}
}
await client.send(new AdminLinkProviderForUserCommand(linkParam))
}
return event
}
catch (err) {
console.error(err)
}
}
No longer true in 2023 - START
However, it is a bad UX as the first sign in with federated identity will only create the user but not allowing it to authenticate. However, the subsequent sign in with federated identity will show no such issue. Let me know, if you get any other solution for that first sign in.
No longer true in 2023 - END
It's also useful to keep email_verified
as true
so that user can recover their password. Especially true if you are using aws-amplify
authenticator. This should be in your post authentication trigger.
const {
CognitoIdentityProviderClient,
AdminUpdateUserAttributesCommand,
} = require('@aws-sdk/client-cognito-identity-provider')
const client = new CognitoIdentityProviderClient({
region: process.env.REGION,
})
exports.handler = async(event, context, callback) => {
try {
const {
userPoolId,
userName,
request: {
userAttributes: { email_verified }
}
} = event
if (!email_verified) {
const param = {
UserPoolId: userPoolId,
Username: userName,
UserAttributes: [{
Name: 'email_verified',
Value: 'true',
}],
}
await client.send(new AdminUpdateUserAttributesCommand(param))
}
return event
}
catch (err) {
console.error(err)
}
}