-
Notifications
You must be signed in to change notification settings - Fork 819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Automatic Social Account Merging #4427
Comments
Thanks for the feature request @techdragon we will investigate merging social accounts by default |
Any update on this, as we are have deployed our app and currently, this is an issue for us and now we are stuck at login with apple issue. we do have a website so this feature is mandatory because the user logged in with apple cannot use that in web or android, so the user should be able to login using the given email without any issue. |
This seems like such a basic problem that I was also kind of shocked to discover it's not supported by default. There are a bunch of other issues and comments on blog posts asking about this as well. Apparently the workaround is to use AdminLinkProviderForUser in a pre signup trigger to link to an existing user, but it definitely feels like something Amplify should be handling. As it stands, the same person can sign up with email or an identity provider, forget how they signed up, then sign in with a different identity provider and a new account would be created for them, which is definitely not ideal. |
Any update on this issue? |
The way I'm currently handling this is: In the PreSignup hook, if
|
@ianmartorell Thank you so much for sharing your implementation. It is extremely helpful. I test it and work well. For others who are interested, below is a sample code of mine used for pre sign up lambda based on @ianmartorell's implementation const aws = require('aws-sdk');
const { v4: uuidv4 } = require('uuid');
exports.handler = async (event, context, callback) => {
// ... skip other codes
// If trigger source is external provider
if (event.triggerSource === 'PreSignUp_ExternalProvider') {
const cognitoProvider = new aws.CognitoIdentityServiceProvider({
apiVersion: '2016-04-18',
});
try {
// Get user based on email
const listUserParams = {
UserPoolId: event.userPoolId,
AttributesToGet: null, //null returns all attributes
Filter: `email = \"${event.request.userAttributes.email}\"`,
Limit: 1,
};
const listUsersRes = await cognitoProvider
.listUsers(listUserParams)
.promise();
let destinationAttributeValue;
// If user not found, create user
if (listUsersRes.Users.length === 0) {
console.log('User not found');
const {
email = '',
given_name = '',
family_name = '',
phone_number = '',
} = event.request.userAttributes;
const newPassword = uuidv4(); // or use your own implementation
const newUserParams = {
UserPoolId: event.userPoolId,
Username: email || phone_number,
MessageAction: 'SUPPRESS',
TemporaryPassword: newPassword,
UserAttributes: [
{
Name: 'email',
Value: email,
},
{
Name: 'email_verified',
Value: String(!!email), //auto verify email if provided
},
{
Name: 'given_name',
Value: given_name,
},
{
Name: 'family_name',
Value: family_name,
},
{
Name: 'phone_number',
Value: phone_number,
},
{
Name: 'phone_number_verified',
Value: String(!!phone_number),
},
],
};
const newUser = await cognitoProvider
.adminCreateUser(newUserParams)
.promise();
// Confirm new user
const setPasswordParams = {
Password: newPassword,
UserPoolId: event.userPoolId,
Username: newUser.User.Username,
Permanent: true,
};
await cognitoProvider.adminSetUserPassword(setPasswordParams).promise();
destinationAttributeValue = newUser.User.Username;
}
// If user found, simply set username
else {
console.log('User found');
destinationAttributeValue = listUsersRes.Users[0].Username;
}
// Link User
console.log('Link user');
let [sourceProviderName, sourceAttributeValue] = event.userName.split(
'_'
);
sourceProviderName =
sourceProviderName[0].toUpperCase() + sourceProviderName.slice(1);
const adminLinkParams = {
DestinationUser: {
ProviderAttributeValue: destinationAttributeValue,
ProviderName: 'Cognito',
},
SourceUser: {
ProviderAttributeName: 'Cognito_Subject',
ProviderAttributeValue: sourceAttributeValue,
ProviderName: sourceProviderName,
},
UserPoolId: event.userPoolId,
};
await cognitoProvider.adminLinkProviderForUser(adminLinkParams).promise();
// Finish linking, throw error to frontent
callback(new Error(`LINKED_EXTERNAL_USER_${sourceProviderName}`), event);
} catch (error) {
callback(error, event);
}
}
callback(null, event);
}; Also make sure to add permission to your pre sign up lambda for "lambdaexecutionpolicy": {
...
"Properties": {
...
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
...
},
{
"Effect": "Allow",
"Action": [
"cognito-idp:ListUsers",
"cognito-idp:AdminLinkProviderForUser",
"cognito-idp:AdminCreateUser",
"cognito-idp:AdminSetUserPassword"
],
"Resource": {
"Fn::Sub": [
"arn:aws:cognito-idp:${region}:${account}:*",
{
"region": {
"Ref": "AWS::Region"
},
"account": {
"Ref": "AWS::AccountId"
}
}
]
}
} |
@ianmartorell One of the problem I run into is that calling |
@xitanggg Glad to hear it works for you too! Oh yes, I have that issue with |
@ianmartorell Thanks for your prompt response. That is actually what I am thinking as well using |
Yeah it's pretty stupid... Unfortunately I don't think there's any other way. As |
I was able to map google's
|
When the Pre sign up hook run it will just create the link but then users will have to sign in again in order to complete the process and this run in two steps, how can i sign in user with external provider right after the link is complete? |
I ran into the same issue and explained my workaround in the first bullet point here: #4427 (comment). |
Can you please be more specific on how you are catching this error on the Frontend? since using the Amplify api will force a redirect and the error wont be presented on the function that triggered the sign in. |
The exact implementation varies based on the front end framework you use and the error message you throw. The high level idea is that
In React, I simply create a useEffect hook to do so and use the hook at my redirect page. import { useRef, useEffect } from 'react';
import { useHistory } from 'react-router-dom';
import queryString from 'query-string';
import { Auth } from 'aws-amplify';
const autoExternalSignIn = async (provider) => {
try {
await Auth.federatedSignIn({ provider: provider });
} catch (error) {
console.log('Error auto sign in: ', error);
}
};
/**
* useAutoExternalSignIn hook attempts to auto sign in user after users register
* an account with Google / Facebook external provider.
*
* When user first registers an account using Google / Facebook, a native user is created
* in AWS Cognito and an error is throwed to the front end when the process is succeeded.
*/
const useAutoExternalSignIn = () => {
const numRetry = useRef(0);
const history = useHistory();
const params = queryString.parse(history.location.search);
const errorDes = params.error_description;
useEffect(() => {
if (numRetry.current < 2 && errorDes) {
if (errorDes.includes('LINKED_EXTERNAL_USER_Facebook')) {
autoExternalSignIn('Facebook');
numRetry.current += 1;
}
if (errorDes.includes('LINKED_EXTERNAL_USER_Google')) {
autoExternalSignIn('Google');
numRetry.current += 1;
}
}
}, [errorDes]);
};
export default useAutoExternalSignIn; |
Testing @xitanggg implementation i sometimes get the following error when first signup with google |
@oahmaro Interesting, this is my implementation and I haven't encountered this issue. I am not sure what might cause it but I would suggest |
@oahmaro I also faced the same issue. But later found out that I already had an account in Cognito with the Google account I'm trying. I tried to signup before setting up the pre signup hook. You'll have to clear them out before trying this. |
hi @xitanggg , can you please share your implementation on post authentication trigger to fix facebook signing overriding the email_verified value? This would help me a lot as i am not familiar with Cognito API |
Well not really, this issue happens to me even if i don't have any account, i am still investigating the cause |
I was getting this too! It drove me insane for days, I checked the API calls over and over and I was definitely passing the Cognito user as a DestinationUser. In the end it stopped happening, and as weird as it sounds I think it was because I increased the CPU and memory used by the lambda functions. I did it through editing the CloudFormation templates, I can check what I changed exactly in a few hours. |
@oahmaro You can verify the email in the pre-signup hook right after linking the accounts like this: await cognitoProvider
.adminUpdateUserAttributes({
UserAttributes: [
{
Name: 'email_verified',
Value: 'true',
},
],
Username: destinationAttributeValue,
UserPoolId: event.userPoolId,
})
.promise(); To confirm and enable the user account, you can do the following: await cognitoProvider
.adminConfirmSignUp({
Username: destinationAttributeValue,
UserPoolId: event.userPoolId,
})
.promise(); Make sure you set the proper permissions in the cloudformation-template.json: "Action": [
"cognito-idp:ListUsers",
"cognito-idp:AdminLinkProviderForUser",
"cognito-idp:AdminConfirmSignUp",
"cognito-idp:AdminUpdateUserAttributes"
], |
Hi @ianmartorell this would help a lot. Thanks |
So basically in my PreSignup template I have this: {
"Resources": {
"LambdaFunction": {
"Properties": {
"Handler": "index.handler",
"MemorySize": 2048, <-- Add this line
}
},
},
} Note that although this increases memory usage and it sounds like your functions will be more expensive, they actually run a lot faster because CPU speeds are tied to memory size in lambda functions, and the faster they run the cheaper they are. I haven't run actual benchmarks, but the speed increase was reeeally noticeable in my PostAuthentication hook. I used to have to wait a good couple of seconds after logging in, and now it feels instant. |
@ianmartorell this seems to fix the issue, speaking of speed this whole signup trigger takes 15 seconds to sign in user in the first time, and 8 seconds after account is linked, which seems to be long. anyone tested the time it takes? |
Actually nevermind my metrics as they are inaccurate, after building my app it actually was much faster. |
Haha, good catch @ianmartorell! I actually suspect memory might be the cause, but wasn't 100% sure. I always have my lambda memory size up, in this case to 512MB The default lambda memory size is 128MB, which is stupid, because the @oahmaro my users are coming from google provider and they got mapped correctly, so I haven't written the code to overwrite facebook provider yet (at least not now, may be I will in the next month or so). The Cognito API isn't too difficult to understand. You just need to spend some time reading the docs. As mentioned, |
@xitanggg I'm using forceAliasAuth=true so that I can sign-in user through username(preferred username) or verified email. My only required attribute is email for sign-up a user. Your code is working for 1st sign-up with email then use same email as signin with Google(but I'm not getting error that ) but when I'm trying fresh new sign in with google it is not working(it is not creating an user entry). my code: and one thing username can't be email as forceAliasAuth=true. exports.handler = async (event, context, callback) => {
}; |
@ianmartorell @xitanggg this is not working for me when fresh new sign up with google & im using force alias auth and only requried attirbute is email and user can sign up with email & preferred username and second thing I have mapped username in post conformation function when there is fresh signup with with Google It will not trigger post confirmation as we are throwing error.
|
@xitanggg @ianmartorell @THPubs specially adminCreateUser showing error: |
This worked initially for mitigating the confusing error. Then I added a function call to set the user's email_verified to true AFTER linking the accounts, and no matter how much I increase memory, we're back at |
Three and a half years later, Cognito doesn't yet support this natively... It's unbelievable |
would love to thumbs up this issue to vote support for this feature request |
Amplify, please add multi social account linking. This would take you few weeks, why has it been 4 years? |
Is your feature request related to a problem? Please describe.
Automatic Social Account Merging does not occur by default, #4208 is the original report, but the author closed it after finding their own solution.
Describe the solution you'd like
Automatically merge social accounts based on one of two criteria:
Describe alternatives you've considered
I could build this myself with a bunch of lambdas and other stuff.
Additional context
This sort of functionality is the default in the majority of authentication services I have used. It was genuinely shocking to discover this was not the case while I was searching through the repo's GithHub issues for a different problem and came across #4208. While I'm sure this is primarily a case of terrible default behaviour in the underlying Cognito service, there is a workaround, and from what I can tell, Amplify is trying to be a "we do the boilerplate stuff for you" type of tool, so handling this kind of workaround/boilerplate feels like something Amplify should be doing.
The text was updated successfully, but these errors were encountered: