Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AWS Amplify: How to map social providers attributes for an email based authentication?

I have a React web application built with AWS Amplify I have added authentication with Cognito user pools, I am not using usernames, I have selected the login using email/phone only, I don't want usernames, but Cognito creates a random username anyway.

I want that the user to login using their email or using one social provider (Facebook or Google) and it shouldn't matter, they should have access to the same account, based on the email.

Pretty normal practice, I think. However, when I first tried to log in with a social provider I noticed that instead of Cognito gives me the same account, it created a new one, with a different username and with EXTERNAL_PROVIDER as account status on the user pool

So, I thought it might be just some mapping being made incorrectly, I went to the Attributes mapping on the Federation section, and I saw that the Facebook Id and Google Sub were being assigned to the username, I tried to remove it and for my surprise, it was assigned back to the username. Then I thought "I can just create a custom attribute to store that information and it should be fine".

So I did this, created one attribute for Facebook, one for GoogleId... tried again...nope, still getting back to username, but no error message, nothing...

I went for help on the documentation and found this

Currently, only the Facebook id, Google sub, login with Amazon user_id, and Sign in with Apple sub attributes can be mapped to the Amazon Cognito User Pools username attribute.

If this always associate each provider Id with the username, then, there's no way to merge those accounts I thought it could be that I need to setup a federated identity pool, but reading about it it seems that it is used to give IAM roles/permission to the external users, which I don't want to.

Any idea of how can I achieve that?

like image 957
dfranca Avatar asked May 08 '20 20:05

dfranca


Video Answer


1 Answers

After digging a bit into it, I found the solution. In summary, you should:

  • Create a trigger on Cognito for the Pre-Signup to a lambda function
  • This lambda function should find the corresponding account and link both users
  • Return the event from the lambda function

You can create the trigger from Amplify cli, running amplify auth update, doing the "Walkthrough all the auth configurations", in the end, it is gonna ask you if you want to create the trigger, confirm it and select the Pre Signup Trigger

Then edit the function file created, the default runtime is nodejs, I have changed mine to Python

This is the code I'm using

import boto3

client = boto3.client('cognito-idp')


def handler(event, context):
    print("Event: ", event)
    email = event['request']['userAttributes']['email']

    # Find a user with the same email
    response = client.list_users(
        UserPoolId=event['userPoolId'],
        AttributesToGet=[
            'email',
        ],
        Filter='email = "{}"'.format(email)
    )

    print('Users found: ', response['Users'])

    for user in response['Users']:
        provider = None
        provider_value = None
        # Check which provider it is using
        if event['userName'].startswith('Facebook_'):
            provider = 'Facebook'
            provider_value = event['userName'].split('_')[1]
        elif event['userName'].startswith('Google_'):
            provider = 'Google'
            provider_value = event['userName'].split('_')[1]

        print('Linking accounts from Email {} with provider {}: '.format(
            email,
            provider_value
        ))

        # If the signup is coming from a social provider, link the accounts
        # with admin_link_provider_for_user function
        if provider and provider_value:
            print('> Linking user: ', user)
            print('> Provider Id: ', provider_value)
            response = client.admin_link_provider_for_user(
                UserPoolId=event['userPoolId'],
                DestinationUser={
                    'ProviderName': 'Cognito',
                    'ProviderAttributeValue': user['Username']
                },
                SourceUser={
                    'ProviderName': provider,
                    'ProviderAttributeName': 'Cognito_Subject',
                    'ProviderAttributeValue': provider_value
                }
            )
    # Return the event to continue the workflow
    return event
like image 137
dfranca Avatar answered Oct 22 '22 10:10

dfranca