Merge two accounts in Cognito created with same email (email and social) - amazon-cognito

In my app, I have Amazon Cognito Hosted UI configured to handle Authentication by email and social (facebook and google).
The problem:
When a user create an account by email (ex: gmail) and later sign up by social (ex: same gmail), I have two accounts created in Cognito.
Question:
How merge these two accounts created with the same email?
Thanks for your help.
Edit
For those interested, I created an issue on aws amplify:
https://github.com/aws-amplify/amplify-js/issues/2754

You cannot link them however you can decide the behavior by setting the forceAliasCreation parameter in ConfirmSignUp API. If you set it as false it will throw an error if a user has already registered with same email/phone number. If you set it as true, the old account will be deactivated and the new one will be the only one he can use. You can read more about this behavior here

Related

Signing in with Google using an existing account - The correct way

Our website allows users to create a new account using a registration page where we collect username, email, passwords, avatar, etc. The users activate their account and then login and browse, buy, comment, etc like normal.
We recently added the Login with Google button to our site as an additional option. Currently the system does the following:
Get a post request from Google
Verify the signature on the JWT and prepare the credentials
Do some security checks on the request
Check if the email exists with an existing user in our system - if it does, authorize them and login
If the email and sub don't exist, create a new account and load the data from Googles credential POST to make a new account
The conflict here is whether or not we should be doing step 4 on existing accounts that were not created using Google or if those accounts should be converted to Google only accounts when they login.
For example,
I register with john#gmail.com as my account name with a password created on the register page. One day, I accidentally, or on purpose, click Sign in with Google. The system sees my email from the oauth login and finds my account already in the system.
Should it:
Log me into the account without checking password, since its already my Google account, and keep everything else the same.
Give me an error that my email is already in use on another account and abort the login process.
Convert the account to a Google only sign-in and remove the password to prevent me from logging in without using Google in the future.
Update the account with the Google sub id but keep the password option and allow them to reset their website password independently from Google should they wish to "unlink" their Google account in the future.
I believe step 4 would be the most logical, but as we have not implemented this before we want to follow the standard that most other developers would use - or maybe there's an even better way.

NextAuth - OAuthAccountNotLinked - Imported data from another website - Autolinking

I have OneLogin setup in my application and is working fine. Am using MongoDB database for storing the sessions, accounts and users.
And now, I imported user data from my old WordPress website(which doesn't uses OneLogin, but the native WordPress login).
So basically I imported the user data from WordPress and populated the users collection using the email_id, name, etc. When I login with the OneLogin into my application, it throws the error saying OAuthAccountNotLinked. When researched I can see that you are not recommending the auto-linking of user accounts for safety reasons. But in my case, it's a OneLogin provider that my client's organization that has now started using. And new OneLogin user registrations are manually approved by the admin. So security wise it won't be a problem. We are only using OneLogin as auth provider!
How can I setup auto-linking in this scenario? Because I have 10,000s of Users in my MongoDB collection(imported from old WordPress website). And each User is being requested to manually register at OneLogin using the same email id they were using before in the old WordPress website and is manually approved within the OneLogin.
Thanks
Quote right from the original site
Automatic account linking on sign in is not secure between arbitrary providers - with the exception of allowing users to sign in via an email addresses as a fallback (as they must verify their email address as part of the flow).
When an email address is associated with an OAuth account it does not necessarily mean that it has been verified as belonging to account holder — how email address verification is handled is not part of the OAuth specification and varies between providers (e.g. some do not verify first, some do verify first, others return metadata indicating the verification status).
With automatic account linking on sign in, this can be exploited by bad actors to hijack accounts by creating an OAuth account associated with the email address of another user.
For this reason it is not secure to automatically link accounts between arbitrary providers on sign in, which is why this feature is generally not provided by authentication service and is not provided by NextAuth.js.
Automatic account linking is seen on some sites, sometimes insecurely. It can be technically possible to do automatic account linking securely if you trust all the providers involved to ensure they have securely verified the email address associated with the account, but requires placing trust (and transferring the risk) to those providers to handle the process securely.
Examples of scenarios where this is secure include with an OAuth provider you control (e.g. that only authorizes users internal to your organization) or with a provider you explicitly trust to have verified the users email address.
Automatic account linking is not a planned feature of NextAuth.js, however there is scope to improve the user experience of account linking and of handling this flow, in a secure way. Typically this involves providing a fallback option to sign in via email, which is already possible (and recommended), but the current implementation of this flow could be improved on.
Providing support for secure account linking and unlinking of additional providers - which can only be done if a user is already signed in already - was originally a feature in v1.x but has not been present since v2.0, is planned to return in a future release.
You probably need to write your own implementation to handle such a situation. And call that implementation on each provider call back. Like a check, the email already exists in DB, through an extra level of verification like OTP etc. Finally, when everything passes, let the user in and store some extra info in the DB for future reference.
Since I wasn't able to find a straight forward solution, I come up with a workaround. It's not a perfect solution, but does the job. Maybe someone else will post a better solution here.
So what I did is, I have all the old user data imported to the users collection in my MongoDB database. And NextAuth uses this users, accounts and session collections to store the User and related data. Btw am storing the session data in database.
In my case, we have sent unique OneLogin registration URLs(created from the OneLogin Admin Dashboard) to our existing users for them to create a new account in OneLogin. Then when the User registers an account at OneLogin, the admin approves/rejects the User. If they are approved, they will be eligible to login to our app using their OneLogin credentials.
What I noticed is that, when a User tries to login, NextAuth checks the email field in users collection to find a matching record. Since we imported the Users from WordPress database, we only have records in users collection and nothing on the accounts collection. In accounts collection, NextAuth is storing the access_token, user_id (mapping to users collection), provider details, etc.
So during login, the NextAuth does the internal checks and finds the existing email in the users collection, but it fails to identify the user as there's no info about the provider(OneLogin) details.
Record from accounts collection:
So what I did is, updated all records in users collection by appending a _TEMP to the email field's values. For example, if there's a record with the value abc#abc.com, it will become abc#abc.com_TEMP
Then what I did is, I wrote an API route(/api/check_old_account) in my NextJS application, where it does the following:
gets the currently logged in User's email id (from NextAuth session)
searches the users collection for the same email id with a "_TEMP" at the end
if there exists a _TEMP version of the currently logged in User, then return a response saying an old account exists
Then in the HOME page, I wrote the code to call the above mentioned api (/api/check_old_account) if the User is logged in. This call is made only once in the HOME page, when everything is loaded. Here the login using OneLogin works without errors because we renamed the existing email id in users collection by appending _TEMP. So when User logins, the users collection inserts a new record with their email and related data to the accounts collection also. Basically there would be two records in users collection for that User now. One is their original email id record that got inserted now when the logged in (eg: abc#abc.com) and their old imported account (eg: abc#abc.com_TEMP). The reason why I wrote the code to call the API in the HOME page only is because, after User logs in, they will be redirected to the HOME page. So thought it won't be necessary to write the code globally or somewhere else.
So, if the above API response says that there's an old account that exists, I display a warning popup to the User saying that there's an old account existing in the database and asks whether they want to link that old account. If the User presses YES button, I make an API call to /api/link_old_account. In this API route, the code is written to perform this:
if there's a _TEMP version email of the currently logged in user in users collection, find the respective record _id that has mappings in the accounts collection.
then change the value of the userId field of that respective record(currently logged in user id) in accounts collection, with the user id of the record with the _TEMP version email.
then delete the record with the _id of the currently logged in user
then update the email field by removing the _TEMP from it
then deletes the records that matches the currently logged in user in the userId field of sessions collection. So that the currently logged in sessions of this User would be invalidated.
then redirects the user back to the HOME page using res.redirect(307, '/')
The solution seems to be working fine so far.

How to disable AWS Cognito User Pool account created via Identity Provider?

Any Cognito User Pool gurus out there? I've been using Cognito for a while now but this one has me a bit stumped.
We allow users to sign up and sign in using social accounts like Facebook which are set up as Identity Providers in the User Pool.
Users need to complete a custom registration form before they can use the main app - we don't use the hosted UI for login or signup
One step of the custom registration process allows the user to indicate which social provider then want to use
This allows us to pull back the users email, first and last names from the social provider which is great - we use a cognito client and callback to do this currently
But in doing so, this provisions a user within the Userpool before the registration process is complete - in fact this makes sense- in order for Cognito to provide us the user info it needs to have called into the social providers /userinfo endpoint to populate the user data
So, the issue we now have is that whilst the user is half way through the registration process I have a confirmed user account - eg. before the user has completed the registration process
This is an issue because a user could sign into the the app using their social login without ever have completed the registration process
So as I see it I have two options:
PostConfirmation Lambda trigger which uses the cognito-idp SDK to disable the user just after it was confirmed
Don't use Cognito to obtain the user info like firstname, lastname, email, picture etc - however this would require us to write a solution for every current and future social provider which isn't something I'm keen on
Am I missing something obvious?
Thanks in advance!
I would say PostConfirmation Lambda trigger is a good approach - however instead use adminDisableProviderForUser to disable the user from signing in with the specified external (SAML or social) identity provider
adminDisableProviderForUser
You can later call adminLinkProviderForUser to link the existing user account in the user pool to the external identity provider.
adminLinkProviderForUser
An alternative solution is to prevent the user from signing in if they have not fully completed the registration process via a Pre Authentication Lambda Trigger checking for a unique identifier with respect to your completed registration process
The simplest solution in the end for us was a Pre Token Generation Trigger in Cognito like this:
exports.handler = async (event) => {
if(event.triggerSource==="TokenGeneration_HostedAuth") {
//check db/api etc to see if we have a valid registration stored for user
if(!hasCompletedRegistration) {
//throw auth exception which we can catch on the frontend to inform user
throw new Error("REGISTRATION_NOT_COMPLETE")
}
}
return event
};
For username/password sign ins the TriggerSource will be TokenGeneration_Authentication
For federated/social sign ins the TriggerSource will be TokenGeneration_HostedAuth

same gmail account has the same user ID in Auth0 even after deletion

I am implementing a functionality of removing a user account (the user will have the possibility to delete its account).
I am using Auth0 as authentication provider. If I log in the app using a Gmail account, Auth0 will create the account with user ID value of XXXXX. After I delete the user (through API or from users management section), if I try to access the app again with the same Gmail account then the user ID will still be XXXXX.
This is a problem in my case because I need to anonynimize the data and the other details should never be visible.
I can solve the problem by changing the the ID in my database but for the moment I would like to keep it.
Is this the normal behavior of auth0?
Good morning tzortzik! I work with the Auth0 Community team and after confirming with one of our senior engineer, some connections the user ID is built based on details originating from the external IDP (Google in this case). That being said, if you remove a user in Auth0 for this instance, the very next time the user logs in/signs up they will receive the same ID that originated from the external IDP. I hope this helps clear some things up. Thanks!

creating a "login with" provider pseudocode if account already exist

I am trying to figure out how to address the issue of what to do when the email already exists in certain situations. I am currently using firebase, but I believe it would be the same problem no matter what software you use. Is there a standard way to do this?
Example:
Login with Email / Register
if "email" exists then error( EMAIL_EXISTS )
else create account & login
user must verify email
EMAIL_EXISTS = "Please login with your <%provider%> account, your email already exists"
Login with Provider (google, fb, twitter, etc) / Register
If "email" exists then add provider to account providers
else create account (possibly ask for new password) & login
Login with Email
Click Connections / Providers
Add Google / Facebook / Twitter etc to account
Is there a standard way to do this? I feel like I am missing some steps. I keep seeing many apps that do not address the issue and you must have a different account for each provider which is different than your email account. Obviously a good programmer thinks about these things.
How should I go about this?
The first 4 steps are pretty much how you should implement it with Firebase Authentication if you have the One account per email address setting enabled (which it is by default) in your Firebase Authentication console.
But if you want this flow in your app, I highly recommend checking if there is a FirebaseUI library with Auth for your platform. It exists for iOS, Android, and Web, and implements many common auth flows in a consistent way.