Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@onhate
Last active February 28, 2024 03:33
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save onhate/67797decbb5a262786cea405f8bf29a1 to your computer and use it in GitHub Desktop.
Save onhate/67797decbb5a262786cea405f8bf29a1 to your computer and use it in GitHub Desktop.
Merge AWS Cognito External Provider User with Cognito User on Pre SignUp
import { PreSignUpTriggerEvent, PreSignUpTriggerHandler } from 'aws-lambda';
import { CognitoIdentityServiceProvider } from 'aws-sdk';
const cognito = new CognitoIdentityServiceProvider();
const knownProviderNames = {
google: 'Google',
facebook: 'Facebook'
};
const getProviderName = async (userPoolId: string, providerName: string) => {
if (knownProviderNames[providerName]) {
return knownProviderNames[providerName];
}
const { Providers } = await cognito.listIdentityProviders({ UserPoolId: userPoolId }).promise();
for (const provider of Providers) {
if (provider.ProviderName.toLowerCase() === providerName.toLowerCase()) {
return provider.ProviderName;
}
}
};
const tryMergeUserAccounts = async (event: PreSignUpTriggerEvent) => {
const { userPoolId, userName } = event;
const { email } = event.request.userAttributes;
const [provider, ...providerValues] = userName.split('_');
const providerValue = providerValues.join('_');
// merge social provider with existing cognito user by email
if (provider.length > 0 && providerValue.length > 0) {
const [{ Users }, providerName] = await Promise.all([
cognito
.listUsers({
UserPoolId: userPoolId,
AttributesToGet: ['email'],
Filter: `email = "${email}"`,
Limit: 1
})
.promise(),
getProviderName(userPoolId, provider)
]);
if (providerName && Users.length > 0) {
for (const user of Users) {
await cognito
.adminLinkProviderForUser({
UserPoolId: userPoolId,
DestinationUser: {
ProviderName: 'Cognito',
ProviderAttributeValue: user.Username
},
SourceUser: {
ProviderName: providerName,
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: providerValue
}
})
.promise();
}
// return true to indicate users were merged
return true;
}
}
return false;
};
export const handler: PreSignUpTriggerHandler = async (event, _, callback) => {
// continue the flow only if did not link providers
const wereUsersMerged = await tryMergeUserAccounts(event);
return wereUsersMerged ? undefined : callback(null, event);
};
@coyksdev
Copy link

coyksdev commented Sep 2, 2021

Hi, Thanks for this. It worked for me. However, it throws an error "Already found an entry for ...". How do you handle it? I also return undefined but still, it throws an error.

@onhate
Copy link
Author

onhate commented Sep 2, 2021

@campanagerald I show the user a friendly message based on this error asking for the user to login again because there was already another account found and it was merged. There's no way around it, trust me hehe

@coyksdev
Copy link

coyksdev commented Sep 3, 2021

Thanks, @onhate!

@coyksdev
Copy link

coyksdev commented Sep 3, 2021

Do you have an idea how to do it when the user login from an external provider first? it seems that Cognito doesn't link if the external user is already created.

@onhate
Copy link
Author

onhate commented Sep 3, 2021

@campanagerald I consider that a security flaw because only knowing your email I could take over you account, on the other hand merging an existing cognito account with an external provider is less risky because you could consider that the third party certifies the user is the owner of that email. The best UX flow in that case would be to have a section on "my account" when the user is authenticated to "create a password" that would then create an username/password account and merge them.

@njeirath
Copy link

njeirath commented Oct 7, 2021

I consider that a security flow because only knowing your email I could take over you account

@onhate I agree with this if the pool is not setup to use email verification but if it is then the user would still have to prove they have access to the code (and thus the email account) before they could login correct?

I'm just wondering if there is some other case I'm not thinking of where it would be a security risk to allow linking of an existing social account with a new email/password account if email verification codes are enabled.

@johnny68
Copy link

johnny68 commented Feb 9, 2022

Hey, thanks for this snippet.
And I was facing some issues with SAM + TS.
Could you help me with that, if you have a similar setup ?

@onhate
Copy link
Author

onhate commented Feb 9, 2022

@johnny68 I have an updated version of this, will update later

@onhate
Copy link
Author

onhate commented Feb 9, 2022

@johnny68 updated to suport dynamic providers other than facebook and google

@Samrose-Ahmed
Copy link

Don't use callback in an async handler, just return.

@AsitDixit
Copy link

@onhate @Samrose-Ahmed @johnny68 @njeirath @coyksdev can any one provide code for js please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment