AWS Developer Blog

CognitoAuthentication Extension Library Developer Preview

by Sam Mousigian | on | in .NET | Permalink | Comments |  Share

We are pleased to announce the Developer Preview of the CognitoAuthentication extension library. This library simplifies the authentication process of Amazon Cognito User Pools for .NET 4.5, .NET Core, and Xamarin developers. Many customers reported that they directly implemented the Secure Remote Password (SRP) protocol themselves. This process requires hundreds of lines of difficult cryptography implementation. Our goal in creating the CognitoAuthentication extension library is to eliminate this hassle and allow you to use the authentication methods for Amazon Cognito User Pools with only a few short method calls. We also want to make the process more intuitive. Instead of having to read through pages of documentation to know which parameters to send for each type of authentication, we ask for the necessary fields in the corresponding request parameter of each authentication method and then produce the proper service request on your behalf. The library is built on top of the Amazon Cognito Identity Provider API to create and send these API calls to authenticate users. We hope this library helps you use Amazon Cognito User Pools to authenticate users, and we look forward to your feedback.

Getting Started

To set up an AWS account and install the AWS SDK for .NET to take advantage of this library, see Getting Started with the AWS SDK for .NET. Create a new project in Visual Studio and add the CognitoAuthentication extension library as a reference to the project. You can find it in the NuGet gallery as AWSSDK.Extensions.CognitoAuthentication. Using the library to make calls to the Amazon Cognito Identity Provider API from the AWS SDK for .NET is as simple as creating the necessary CognitoAuthentication objects and calling the appropriate AmazonCognitoIdentityProviderClient methods. The principal Amazon Cognito authentication objects are:

  • CognitoUserPool objects store information about a user pool, including the poolID, clientID, and other pool attributes.
  • CognitoUser objects contain a user’s username, the pool they are associated with, session information, and other user properties.
  • CognitoDevice objects include device information, such as the device key.

You can use AnonymousAWSCredentials when creating the identity provider client, which results in requests not being signed before they are sent to the service. Any service that does not accept unsigned requests returns a service exception in this case. This is appropriate for releasing in your final product to end users that should not have proper credentials until they are authenticated.

Authenticating with Secure Remote Protocol (SRP)

Instead of implementing hundreds of lines of cryptographic methods yourself, you now only need to create the necessary AmazonCognitoIdentityProviderClient, CognitoUserPool, CognitoUser, and InitiateSrpAuthRequest objects and then call StartWithSrpAuthAsync. We made these structures as lightweight as possible while still providing a large amount of functionality. The InitiateSrpAuthRequest currently only requires the password for the user; all other required information is already stored in the CognitoAuthentication objects. This handles creating and responding to the USER_SRP_AUTH and PASSWORD_VERIFIER challenges during the authentication flow on behalf of the developer, and then returns an AuthFlowResponse object. The AuthenticationResult property of the AuthFlowResponse object contains the user’s session tokens if the user was successfully authenticated. If more challenge responses are required, this field is null and the ChallengeName property describes the next challenge, such as multi-factor authentication. You would then call the appropriate method to continue the authentication flow. The following code snippet shows how the library performs SRP authentication:

using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;

public async void AuthenticateWithSrpAsync()
{
    var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(),
                                                           FallbackRegionFactory.GetRegionEndpoint());
    CognitoUserPool userPool = new CognitoUserPool("poolID", "clientID", provider);
    CognitoUser user = new CognitoUser("username", "clientID", userPool, provider);

    string password = "userPassword";

    AuthFlowResponse context = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
    {
        Password = password
    }).ConfigureAwait(false);
}

Authenticating with Multiple Forms of Authentication

Continuing the authentication flow with challenges, such as with NewPasswordRequired and Multi-Factor Authentication (MFA), is simpler as well. The only things required are the CognitoAuthentication objects, user’s password for SRP, and the necessary information for the next challenge, acquired after prompting the user to enter it. The following code shows one way to check the challenge type and get the appropriate responses for MFA and NewPasswordRequired challenges during the authentication flow:

using System;

using Amazon.Runtime;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;

public async void AuthenticateUserAsync()
{
    var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(),
                                                           FallbackRegionFactory.GetRegionEndpoint());
    CognitoUserPool userPool = new CognitoUserPool("poolID", "clientID", provider);
    CognitoUser user = new CognitoUser("username", "clientID", userPool, provider);

    string password = "userPassword";
    AuthFlowResponse authResponse = null;

    authResponse = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
    {
        Password = password
    }).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
                }).ConfigureAwait(false);
        }
        else if (authResponse.ChallengeName == ChallengeNameType.SMS_MFA)
        {
            Console.WriteLine("Enter the MFA Code sent to your device:");
            string mfaCode = Console.ReadLine();

            authResponse = await user.RespondToSmsMfaAuthAsync(new RespondToSmsMfaRequest()
            {
                 SessionID = authResponse.SessionID,
                 MfaCode = mfaCode
            }).ConfigureAwait(false);
         }
         else
         {
             Console.WriteLine("Unrecognized authentication challenge.");
             break;
         }
    }

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

Similar to the SRP authentication model, if the user is authenticated after these method calls, the AuthenticationResult property of the mfaResponse object contains the user’s session tokens. Otherwise, continue prompting the user for the necessary information required for the next authentication challenge described in the mfaResponse ChallengeName field. As shown above, the main variables to maintain between authentication flow calls are the SessionID and ChallengeName of the AuthFlowResponse. You should also handle the case of an unrecognized challenge. This can occur when there is an out-of-date SDK and if the service introduced a new authentication challenge. It can also occur if the application does not support a certain type of challenge. Here, we simply output this to the user and tell the user that there was an error in the authentication process; however, this scenario can be handled in the way that best suits the application.

Using AWS Resources After Authentication

Once a user is authenticated using the CognitoAuthentication library, the next step is to allow them to access the appropriate AWS resources. This requires you to create an identity pool through the Amazon Cognito Federated Identities console. By specifying your created Amazon Cognito User Pool as a provider using its poolID and clientID, you can allow your Amazon Cognito User Pool users to access AWS resources connected to your account. You can also specify different roles for both unauthenticated and authenticated users to be able to access different resources. These roles can be changed in the IAM console where you can add or remove permissions in the “Action” field of the role’s attached policy. Then, using the appropriate identity pool, user pool, and Amazon Cognito user information, calls can be made to different AWS resources. The following shows a user authenticated with SRP accessing the developer’s different S3 buckets permitted by the associated identity pool’s role:

using System;

using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using Amazon.S3.Model;
using Amazon.CognitoIdentity;
using Amazon.CognitoIdentityProvider;
using Amazon.Extensions.CognitoAuthentication;

public async void GetS3BucketsAsync()
{
    var provider = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(),
                                                            FallbackRegionFactory.GetRegionEndpoint());
    CognitoUserPool userPool = new CognitoUserPool("poolID", "clientID", provider);
    CognitoUser user = new CognitoUser("username", "clientID", userPool, provider);

    string password = "userPassword";

    AuthFlowResponse context = await user.StartWithSrpAuthAsync(new InitiateSrpAuthRequest()
    {
        Password = password
    }).ConfigureAwait(false);

    CognitoAWSCredentials credentials = 
        user.GetCognitoAWSCredentials("identityPoolID", RegionEndpoint.<YourIdentityPoolRegion>);

    using (var client = new AmazonS3Client(credentials))
    {
        ListBucketsResponse response = 
            await client.ListBucketsAsync(new ListBucketsRequest()).ConfigureAwait(false);

        foreach (S3Bucket bucket in response.Buckets)
        {
            Console.WriteLine(bucket.BucketName);
        }
    }
}

Other Forms of Authentication

In addition to SRP, NewPasswordRequired, and MFA, the CognitoAuthentication extension library offers an easier authentication flow for:

  • Custom – Begins with a call to StartWithCustomAuthAsync(InitiateCustomAuthRequest customRequest)
  • RefreshToken – Begins with a call to StartWithRefreshTokenAuthAsync(InitiateRefreshTokenAuthRequest refreshTokenRequest)
  • RefreshTokenSRP – Begins with a call to StartWithRefreshTokenAuthAsync(InitiateRefreshTokenAuthRequest refreshTokenRequest)
  • AdminNoSRP – Begins with a call to StartWithAdminNoSrpAuth(InitiateAdminNoSrpAuthRequest adminAuthRequest)

Call the appropriate method depending on the desired flow, and then continue prompting the user with challenges as they are presented in the AuthFlowResponse objects of each method call. Also call the appropriate response method, such as RespondToSmsMfaAuthAsync for MFA challenges and RespondToCustomAuthAsync for custom challenges. If new authentication methods are created for Amazon Cognito User Pools in the future, they will be added to the library as well.

Conclusion

We hope you find this preview of the CognitoAuthentication extension library useful as it simplifies the authentication process from hundreds of lines of code to only a few. We appreciate all of your feedback. You can provide public feedback on our GitHub page or our Gitter page. Those who prefer not to give public feedback can reach out to our support team by filing a support ticket through the AWS Management Console.