Skip to content

Instantly share code, notes, and snippets.

@onhate
Last active December 19, 2024 13:59
Show Gist options
  • Select an option

  • Save onhate/67797decbb5a262786cea405f8bf29a1 to your computer and use it in GitHub Desktop.

Select an option

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

coyksdev commented Sep 2, 2021

Copy link
Copy Markdown

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

onhate commented Sep 2, 2021

Copy link
Copy Markdown
Author

@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

coyksdev commented Sep 3, 2021

Copy link
Copy Markdown

Thanks, @onhate!

@coyksdev

coyksdev commented Sep 3, 2021

Copy link
Copy Markdown

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

onhate commented Sep 3, 2021

Copy link
Copy Markdown
Author

@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

njeirath commented Oct 7, 2021

Copy link
Copy Markdown

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

johnny68 commented Feb 9, 2022

Copy link
Copy Markdown

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

onhate commented Feb 9, 2022

Copy link
Copy Markdown
Author

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

@onhate

onhate commented Feb 9, 2022

Copy link
Copy Markdown
Author

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

@Samrose-Ahmed

Copy link
Copy Markdown

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

@AsitDixit

Copy link
Copy Markdown

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

@Alex0007

Alex0007 commented Sep 1, 2024

Copy link
Copy Markdown
  return wereUsersMerged ? undefined : callback(null, event);

It should always return event, otherwise user will not be logged-in after merging when using amplify

@TharinduX

TharinduX commented Sep 11, 2024

Copy link
Copy Markdown

Thank you for this!

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