Skip to content

AWS .net SDK returns AliasExistsException incorrectly

0

Hi, we are using these latest versions of AWS SDK Nuget packages that relate to AWS Cognito:

Our user pool is set up like this:

  • Cognito user pool sign-in options: Email
  • Password sign-in only
  • Attributes to verify: Send email message, verify email address
  • Required attributes: email, given_name, family_name
  • Self-service sign-up: Enabled
  • Custom Email Sender Lambda trigger and Custom SMS Sender Lambda trigger have both been defined

So email attribute is an alias for the username.

Users can self sign-up on our web app (which uses AWS Amplify) or users can be created on our back-end which is written in .net 8.0 and uses the AWS SDK's admin methods.

We are trying to build a feature to allow email address to be updated. Currently we are only allowing this via the back-end which uses the following AWS SDK methods for the email address change:

  • IAmazonCognitoIdentityProvider.AdminGetUserAsync
  • IAmazonCognitoIdentityProviderAdminUpdateUserAttributesAsync

The problem we are facing is that whenever we try to call IAmazonCognitoIdentityProviderAdminUpdateUserAttributesAsync for an email address change we get an AliasExistsException with the message An account with the given email already exists. This happens even when no user with the updated email address exists in the user pool.

Apart from getting this exception we also see a 2nd user created with the updated email address and the original user with the old email address remains. We have confirmed that it's a new user by looking at the sub attribute and created/updated timestamps.

When we try the same thing via the AWS Console or AWS CLI it seems to work fine.

Here is what our code is effectively doing. The actual code is not exactly like this, this is a simplified version but the AWS calls are the same.

var adminGetUserResponse = await _amazonCognitoIdentityProvider.AdminGetUserAsync(
    new AdminGetUserRequest
    {
        UserPoolId = userPoolId,
        Username = currentEmailAddress,
    });

List<AttributeType> userAttributeTypes =
[
    new AttributeType { Name = "family_name", Value = updatedFamilyName },
    new AttributeType { Name = "given_name", Value = updatedGivenName },
    new AttributeType { Name = "email_verified", Value = "true" }
];

Dictionary<string, string> clientMetadata = [];

if (!string.IsNullOrWhiteSpace(updatedEmailAddress) &&
    !string.Equals(currentEmailAddress, updatedEmailAddress, StringComparison.OrdinalIgnoreCase))
{
    userAttributeTypes.Add(new AttributeType { Name = "email", Value = updatedEmailAddress });
    clientMetadata.Add("PreviousEmailAddress", currentEmailAddress);
}

if (!string.IsNullOrWhiteSpace(updatedPhoneNumber))
{
    userAttributeTypes.Add(
        new AttributeType { Name = "phone_number", Value = updatedPhoneNumber });
}

await _amazonCognitoIdentityProvider.AdminUpdateUserAttributesAsync(
    new AdminUpdateUserAttributesRequest
    {
        UserPoolId = userPoolId,
        Username = adminGetUserResponse.UserAttributes.First(at => at.Name == "sub").Value,
        UserAttributes = userAttributeTypes,
        ClientMetadata = clientMetadata,
    });

The exception trace looks like this:

	 Amazon.CognitoIdentityProvider.Model.AliasExistsException: An account with the given email already exists.
 ---> Amazon.Runtime.Internal.HttpErrorResponseException: Exception of type 'Amazon.Runtime.Internal.HttpErrorResponseException' was thrown.
   at Amazon.Runtime.HttpWebRequestMessage.ProcessHttpResponseMessage(HttpResponseMessage responseMessage)
   at Amazon.Runtime.HttpWebRequestMessage.GetResponseAsync(CancellationToken cancellationToken)
   at Amazon.Runtime.Internal.HttpHandler`1.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.Unmarshaller.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
   --- End of inner exception stack trace ---
   at Amazon.Runtime.Internal.HttpErrorResponseExceptionHandler.HandleExceptionStream(IRequestContext requestContext, IWebResponseData httpErrorResponse, HttpErrorResponseException exception, Stream responseStream)
   at Amazon.Runtime.Internal.HttpErrorResponseExceptionHandler.HandleExceptionAsync(IExecutionContext executionContext, HttpErrorResponseException exception)
   at Amazon.Runtime.Internal.ExceptionHandler`1.HandleAsync(IExecutionContext executionContext, Exception exception)
   at Amazon.Runtime.Internal.ErrorHandler.ProcessExceptionAsync(IExecutionContext executionContext, Exception exception)
   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.Signer.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
   at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
... <goes into our code>

Here is the AWS CLI call which works as expected and looks to be doing the same thing as what we're doing through the AWS SDK.

aws --profile <awsProfile> cognito-idp admin-update-user-attributes \
  --user-pool-id <userPoolId> \
  --username <subAttribute> \
  --user-attributes Name=email,Value=<updatedEmailAddress> Name=email_verified,Value=true Name=phone_number,Value=<updatedPhoneNumber> Name=family_name,Value=<familyName> Name=given_name,Value=<givenName>

Are we missing something here? Or is there a bug in the .Net AWS SDK?

Thanks for any help you can provide.

2 Answers
0
Accepted Answer

This turned out to be an internal issue to our codebase. When we ran the above code the problem did indeed manifest but it was not because of this code, it was because earlier to this code we had a spurious _amazonCognitoIdentityProvider.AdminCreateUserAsync running, which created the identical user with the to-be email address. Then when the code above came along it naturally found that the to-be email address already existed in the user pool and rightly threw the AliasExistsException. But to the outside observer it looked like running the code caused the extra user along with the exception being thrown.

In conclusion, AWS Cognito functionality works exactly as expected and our team has fixed our codebase to remove the spurious _amazonCognitoIdentityProvider.AdminCreateUserAsync call while eating humble pie.

answered a month ago
-1

Seems the AWS SDK for .NET behavior is expected. The issue lies in how the Username parameter is being passed, use the sub consistently, not the email alias, to avoid duplicate user creation and AliasExistsException.

https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/csharp_cognito-identity-provider_code_examples.html

https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html

https://docs.aws.amazon.com/cli/latest/reference/cognito-idp/admin-update-user-attributes.html

EXPERT
answered a month ago
  • But that is exactly what we're doing. You can see in the code I've posted above that we are passing in the sub value for the username in the _amazonCognitoIdentityProvider.AdminUpdateUserAttributesAsync call.

    Yes we are using the email address as the username in the initial _amazonCognitoIdentityProvider.AdminGetUserAsync call but this works fine. It retrieves the existing user. We grab the sub from this fetched user and use that in the _amazonCognitoIdentityProvider.AdminUpdateUserAttributesAsync call.

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.