aws / aws-aspnet-cognito-identity-provider

ASP.NET Core Identity Provider for Amazon Cognito
https://aws.amazon.com/developer/language/net/
Apache License 2.0
213 stars 89 forks source link

AmazonCognitoIdentityProviderClient Usage #213

Closed IgorPietraszko closed 2 years ago

IgorPietraszko commented 2 years ago

The Question

I have a couple of general question on the use of AmazonCognitoIdentityProviderClient. Here they are: 1) Based on this article (https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-client-side-authentication-flow), when running in AWS Lambda, I should be using the Server-side authentication flow (Admin) rather than the client one (SRP) - is that correct? 2) If the answer to (1) is YES, then I am puzzled with this statement "The server-side app calls the AdminInitiateAuth API operation (instead of InitiateAuth). This operation requires AWS admin credentials. The operation returns the required authentication parameters." What is meant by that? 3) When instantiating the AmazonCognitoIdentityProviderClient locally in my dev environment, I need to pass AWSAccessKeyId and AWSSecretAccessKey - why does it not pick it up from my credentials? 4) How is (3) accomplished when running in an AWS Lambda - do I need to include anything in my config?

Environment


This is a :question: general question

ashishdhingra commented 2 years ago

Hi @IgorPietraszko,

Good afternoon.

Thanks for posting guidance question. Here are the inputs:

  1. Yes, this is correct.

  2. It mean shat AdminInitiateAuth executes the . The IAM credentials/role used to execute the flow should have AmazonCognitoPowerUser policy attached to it. For Lambda you could create an IAM role and attach that role to a Lambda function. (refer HowTo: Implement user sign up and login with AWS Cognito a good article that uses JavaScript. Please use it just for reference).

  3. That should not be the case. Refer Credential and profile resolution to make sure that your credentials are configured properly. For instance, I use the below code for testing Cognito scenarios (take note that we are passing new instance of new AnonymousAWSCredentials() while initializing new instance of AmazonCognitoIdentityProviderClient):

    
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Amazon.CognitoIdentityProvider;
    using Amazon.Extensions.CognitoAuthentication;
    using Amazon.Runtime;

namespace CognitoTest { class Program { private static string clientId = "<>"; private static string poolId = "<>"; private static string userName = "<>"; private static string userPassword = "<>";

    static void Main(string[] args)
    {
        TestUserAuth().GetAwaiter().GetResult();
    }

    static async Task TestUserAuth()
    {
        var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), FallbackRegionFactory.GetRegionEndpoint());
        var userPool = new CognitoUserPool(poolId, clientId, provider);
        var user = new CognitoUser(userName, clientId, userPool, provider);
        string accessToken = null;

        AuthFlowResponse authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
        {
            Password = userPassword,

        }).ConfigureAwait(false);

        while (authResponse.AuthenticationResult == null)
        {
            if (authResponse.ChallengeName == ChallengeNameType.NEW_PASSWORD_REQUIRED)
            {
                Console.WriteLine("Enter your desired new password:");
                string newPassword = Console.ReadLine();

                authResponse = await user.RespondToNewPasswordRequiredAsync(new RespondToNewPasswordRequiredRequest()
                {
                    SessionID = authResponse.SessionID,
                    NewPassword = newPassword
                });
                accessToken = authResponse.AuthenticationResult.AccessToken;
            }
            else if (authResponse.ChallengeName == ChallengeNameType.SMS_MFA)
            {
                Console.WriteLine("Enter the MFA Code sent to your device:");
                string mfaCode = Console.ReadLine();

                AuthFlowResponse mfaResponse = await user.RespondToSmsMfaAuthAsync(new RespondToSmsMfaRequest()
                {
                    SessionID = authResponse.SessionID,
                    MfaCode = mfaCode

                }).ConfigureAwait(false);
                accessToken = authResponse.AuthenticationResult.AccessToken;
            }
            else
            {
                Console.WriteLine("Unrecognized authentication challenge.");
                accessToken = "";
                break;
            }
        }

        if (authResponse.AuthenticationResult != null)
        {
            Console.WriteLine("User successfully authenticated.");
        }
        else
        {
            Console.WriteLine("Error in authentication process.");
        }

        await UpdateAttributes(user);
    }

    public async static Task UpdateAttributes(CognitoUser user)
    {
        try
        {
            Dictionary<string, string> userAttributes = new Dictionary<string, string>();
            userAttributes.Add("custom:testattribute", "0");

            await user.UpdateAttributesAsync(userAttributes);
        }
        catch (Exception ex)
        {
            throw new Exception(ex.Message);
        }
    }
}

}



4. The answer is in input 2 and 3 above.

Hope this helps.

Thanks,
Ashish
IgorPietraszko commented 2 years ago

Thank you @ashishdhingra for your answers. One more question, when trying to call ListUsersAsync on the AmazonCognitoIdentityProviderClient, I am getting the AmazonCognitoIdentityProviderException: The security token included in the request is invalid". Any idea why?

ashishdhingra commented 2 years ago

Thank you @ashishdhingra for your answers. One more question, when trying to call ListUsersAsync on the AmazonCognitoIdentityProviderClient, I am getting the AmazonCognitoIdentityProviderException: The security token included in the request is invalid". Any idea why?

@IgorPietraszko Unfortunately, I'm unable to reproduce the ListUsersAsync() issue. I configured by credentials in [default] profile in AWS credentials file (I have Admin access) and (new AmazonCognitoIdentityProviderClient()).ListUsersAsync(new Amazon.CognitoIdentityProvider.Model.ListUsersRequest() { UserPoolId = poolId }) (as an example) worked. Are you using session credentials, which might have got expired? If yes, then you might want to rotate and update credentials in your profile chain.

IgorPietraszko commented 2 years ago

Fair enough - will try to take another look but I was using current credentials.

I am torn between how to resolve my problem. At the root, my problem is the ability to communicate with different Cognito User Pools from the same code base, depending which tenant I am executing for. Issue (https://github.com/aws/aws-aspnet-cognito-identity-provider/issues/210) is also my issue/question. I see it like this:

1) Using the AmazonCognitoIdentityProviderClient (this issue), I can instantiate it on the fly but a) I cannot have a AppClientSecretKey when defining the app on the User Pool and have to jump through a few more hoops to get the functionality I need. Note that my existing code is not written to work with this paradigm.

2) My application is written to work with the CognitoIdentity added in the Startup:

services.AddCognitoIdentity();
services.AddTransient<CognitoSignInManager<CognitoUser>>();
services.AddTransient<CognitoUserManager<CognitoUser>>();

but it adds CognitoUserPool as a Singleton. So another options I am exploring right now is basically plagiarizing your code behind AddCognitoIdentity() and changing the TryAddCognitoUserPool() method to add CognitoUserPool with a Scoped lifetime:

        private static IServiceCollection TryAddCognitoUserPool(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Scoped) // <-----
        {
            if (!services.Any(x => x.ServiceType == typeof(CognitoUserPool)))
            {
                Func<IServiceProvider, CognitoUserPool> factory =
                    CognitoUserPoolFactory.CreateUserPoolClient;

                var descriptor = new ServiceDescriptor(typeof(CognitoUserPool), factory, lifetime);
                services.Add(descriptor);
            }
            return services;
        }

This seems to work (haven't done extensive testing) and allows me to instantiate CognitoUserPools on a per request basis thus satisfying the basic premise of per tenant (i.e. request specific) CognitoUserPool.

Since my code is written to work with the second model, do you see any issues with the CognitouserPool being created with Scope lifecycle?

Note that I do make provisions to provide tenant specific config when creating the CognitoUserPool through CognitoUserPoolFactory():

private static AWSCognitoClientOptions GetAWSCognitoClientOptions(IConfiguration config, TenantOptions tenantOptions)
        {
            var options = new AWSCognitoClientOptions();
            string configSection = $"{tenantOptions.TenantName}:AWS";

            IConfiguration section;

            if (string.IsNullOrEmpty(configSection))
                section = config;
            else
                section = config.GetSection(configSection);

            if (section == null)
                return options;

            options.UserPoolClientId = section.GetChildren().FirstOrDefault(c => c.Key == ConfigurationClientIdKey)?.Value;
            options.UserPoolClientSecret = section.GetChildren().FirstOrDefault(c => c.Key == ConfigurationClientSecretKey)?.Value;
            options.UserPoolId = section.GetChildren().FirstOrDefault(c => c.Key == ConfigurationUserPoolIdKey)?.Value;

            return options;
        }
ashishdhingra commented 2 years ago

Since my code is written to work with the second model, do you see any issues with the CognitouserPool being created with Scope lifecycle?

@IgorPietraszko I don't see any issue.

github-actions[bot] commented 2 years ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.