Remove external identity from Cognito user

0

We have an existing userpool with standard Cognito users. At some point we enable a external SAML identity provider for a group of users. When the existing users go to login a new user is created by Cognito and linking the external identity provider. We want to keep using the same Cognito user so we update the external provider on the original Cognito user record. But since there is a matching one on this new Cognito user we still get the new user rather than the original.
I have tried using the AdminDisableProviderForUser api call with both ProviderAttributeName=Cognito_Subject and ProviderAttributeName=Cognito. But they both indicate that the Account is not linked to anyone.

Is the ProviderAttributeName different for the linking that Cognito automatically creates for users?

I think I can just disable that new user to solve this but I would rather not introduce code that would allow disabling of users.

David
asked 2 years ago2356 views
1 Answer
1

Hello,

I understand from your use case here that you would like to maintain a single profile in Cognito User Pool for users signing in using external IDPs

When users in cognito sign in using external IDP, a new user profile is created for them in User Pool. This creates two separate profiles for users who signed up using Cognito but afterwards signed in using external idp.

For maintaining a central identity for user, we need to use adminLinkProviderForUser API [1] call. It links an existing user account in a user pool (DestinationUser) to an identity from an external identity provider (SourceUser) based on a specified attribute name and value from the external identity provider. In order to maintain single central record for the user, we need to know the userid for the external provider and link to the existing user.

  • DestinationUser parameter: DestinationUser can be a native User Pool user or a federated user (for example, a SAML or Facebook user). The ProviderName will be Cognito for native users. For a native user, the ProviderAttributeName will be Username and the ProviderAttributeValue will be the actual username in the user pool. For a federated user, it will be the provider-specific user_id.

  • SourceUser parameter: For social identity providers like Facebook, Google or Amazon the ProviderName will be Facebook , Google or LoginWithAmazon. The ProviderAttributeName will be Cognito_Subject and the ProviderAttributeValue for the user can be the same value as the id, sub or user_id value found in the social identity provider token. For SAML identity providers, the ProviderName will be the exact name identifier given to the identity provider in the Cognito User pool IdP configuration. (e.g., "Auth0", "Okta", “AzureAD”, "mySamlProviderName" etc.) The ProviderAttributeName can be any value that matches a claim in the SAML assertion. If you set ProviderAttributeName to Cognito_Subject , Cognito will automatically parse the default unique identifier found in the subject from the SAML token.

Note - We cannot pass native user as the SourceUser in the above CLI command. If we try to do that that, we will receieve the following error:
An error occurred (InvalidParameterException) when calling the AdminLinkProviderForUser operation: SourceProviderName must match a Provider that is configured for the User Pool

====== Draw back of using Pre Signup Lambda trigger =======

In order to link users to an existing identity with Google provider, we use adminLinkProviderForUser in Pre Signup Lambda trigger. However, while attempting to do so we face an issue where we need to provide the user_id for the external provider’s user prior hand itself and need to make the flow work.

During the Pre Signup Lambda trigger, the identity for external provider is yet to be created in the User Pool. The Lambda receives the user id within the event under username. The username for Google provider will be in format “Google_103228613216140529882” where the user id is the string “103228613216140529882” from the username. If we attempt to link the provider in this case, it eventually results in two login attempts for the user.

When we use external provider like Google, it creates a unique identity in the User Pool. This unique identity contains the user_id and the same identity is referred for subsequent logins. Since we want to maintain a single record, we need to delete the user created for external provider while noting down the user id. This user id should be then used for linking the existing user in Cognito.

====== Leveraging Post confirmation Trigger =======

Please note that when we use external provider for the first time to sign in, Cognito actually signs up the user. Thus, the first attempt to sign in for external provider is always a Signup process in Cognito. As the user is signed up, it also requires confirmation and is auto confirmed by Cognito.

Hence, instead of using pre sign up trigger, we can leverage Post confirmation Lambda trigger[2] in this case. The Post confirmation trigger will be only triggered when the user gets confirmed.

The Post confirmation trigger will be only triggered when the user gets confirmed.

The Lambda pseudo code that needs to be followed is as below :

  • The Lambda extracts the userid from the username received in the event parameter.

  • The external provider user is deleted from the User Pool with API call here [3] with the help of username.

  • We then perform admin link provider [4] operation using the extracted user id. #you will need to implement logic to find existing user through list user[5]

Below I am providing you with the tested python code in my test environment (For external IdP like Google Federation) for Lambda for reference of single profile maintenance :

Note - Please make sure your Lambda's IAM Role has IAM permissions to be able to call Admin delete user and Admin link provider for Cognito API calls.

import boto3

def lambda_handler(event, context):
    # TODO implement
    client = boto3.client('cognito-idp')
    
    print (event['userName'])
    
   
    idname = str(event['userName']).split('_')[1]
    print(idname)
    
    response= client.admin_delete_user(
    UserPoolId='us-east-1_2XXXXXXXX9',
    Username= str(event['userName']) )
    
    
    #print (response)
    
    
    response = client.admin_link_provider_for_user(
    UserPoolId='us-east-1_2XXXXXXXX9',
    DestinationUser={
        'ProviderName': 'Cognito',
        'ProviderAttributeName': 'username',
        'ProviderAttributeValue': 'googleUser'
    },
    SourceUser={
        'ProviderName': 'Google',
        'ProviderAttributeName': 'Cognito_Subject',
        'ProviderAttributeValue': idname
    }
)
    
    #print (event)
    return event

========= Additional Testing with SAML provider ========

Also, I created an AzureAD SAML integration with my userpool and noticed that the username looked like below - "azuread_kXXXXX@yaXXXXXXX.onmicrosoft.com" where the user id was instead "kXXXXX@yaXXXXXXX.onmicrosoft.com", hence in the code I made the below changes. At the end the Cognito Native user was linked with the SAML IdP external User while maintaining one single profile in Cognito Userpool user directory.

Code snippet change - 

    response = client.admin_link_provider_for_user(
    UserPoolId='us-east-1_2XXXXXXXX9',
    DestinationUser={
        'ProviderName': 'Cognito',
        'ProviderAttributeName': 'Username',
        'ProviderAttributeValue': 'test3'
    },
    SourceUser={
        'ProviderName': 'AzureAD',
        'ProviderAttributeName': 'Cognito_Subject',
        'ProviderAttributeValue': idname
    }
)
    
Cognito Native User status after linking -

Users - test3
Account Status	Enabled / CONFIRMED
SMS MFA Status	Disabled
Last Modified	Jun 30, 2022 1:18:02 PM
Created	Jun 30, 2022 1:05:21 PM
sub	7XXXXXX-6XXXXXXXXXXXXXXXXXXca94a7
identities		[{"userId":"kXXXXX@yaXXXXXXX.onmicrosoft.com","providerName":"AzureAD","providerType":"SAML","issuer":null,"primary":false,"dateCreated":1656594967158},
email_verified	false
email	XXXXXXXXXXXXXXXXXXXXXXX
ID Token received after federation - 

{
  "at_hash": "SXXXXXXXXXXXXXQ",
  "sub": "7XXXXXX-6XXXXXXXXXXXXXXXXXXca94a7",
  "email_verified": false,
  "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_2XXXXXXXX9",
  "cognito:username": "test3",
  "nonce": "xyq6tkMXXXXXXXXXDT8jyqECQh5YpPUKOEbPpdMrlQ",
  "aud": "1XXXXXXXXXXXXXXXs",
  "identities": [
    {
      "userId": "kXXXXX@yaXXXXXXX.onmicrosoft.com",
      "providerName": "AzureAD",
      "providerType": "SAML",
      "issuer": null,
      "primary": "false",
      "dateCreated": "1656594967158"
    },
  ....
  ....

Here you can see the linked identities under section “Identities" - Once you have linked native User Pool user to external Azure AD user, when the external user logs into the Cognito User Pool a separate user with Account Status EXTERNAL_PROVIDER will not be created. Instead the external federated user will be linked to the native user account based on the specified attribute name and value from the external identity provider.

Thus by leveraging Post confirmation trigger and admin link provider user API we can maintain central identity for the user.

Finally, in regards to AdminDisableProviderForUser, Cognito User Pool provides this API to help remove the link between an existing user account in an User Pool and an external identity provider user account. The next time the external user (no longer attached to the previously linked DestinationUser) signs in, they must create a new user account

Now, in order to delink the Azure AD identity from the existing user account in an User Pool you can use the following CLI command:

aws cognito-idp admin-disable-provider-for-user --user-pool-id us-east-1_G1VobxXXX --user ProviderName=AzureAD,ProviderAttributeName=Cognito_Subject,ProviderAttributeValue=admin@xyz.com

Note - The ProviderName, ProviderAttributeName and ProviderAttributeValue must be the same values that were used for the SourceUser when the identities were originally linked using AdminLinkProviderForUser API call.

If the suggestions above do not assist you with your use case, we might need to troubleshoot based on your configurations. Could you please create a support case instead so we may discuss details on your resource configurations?

Please do not post any sensitive information over re:Post since this is a public platform.

As always, feel free to reach back with any further questions or concerns in the meantime!

References:

[1] https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_AdminLinkProviderForUser.html

[2] https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html

[3] https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-delete-user.html

[4] https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-link-provider-for-user.html

[5] https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/list-users.html

profile pictureAWS
SUPPORT ENGINEER
Yash_C
answered 2 years ago
  • Hey Yash! Thank you for a potential workaround from having to use the PreSignUp trigger. I stumbled upon this blog post because of the issue involving AdminLinkProviderForUser with the PreSignUp trigger. However, I cannot seem to get your approach to work. From what I understand, the end of the oauth flow will result in a code (authorization code) or a token (implicit grant). However, how will we be able to exchange the code for JWT tokens or use the token if the external provider user is deleted? The resulting code gives me an 'invalid code' error and the token gives me a 'user not found' error.

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions