AWS Knowledge Series: Cognito API & Client Application Integration

Sanjay Dandekar
8 min readAug 23, 2020

Read the overview of the Cognito User Pool in the article below:

In this article, let’s get a little hands on. I will cover some of the basic challenges that developers face when they first start working with AWS Cognito.

Workflow Triggers

In the last article I talked about various workflow triggers that can be configured to kick off custom validations / workflows when a specific event happens e.g. user sign-in, pre-sign-up etc. AWS Cognito uses the AWS Lambda — a core technology of AWS server less technology stack — for this purpose. So the steps to configure a trigger involves following steps:

  • Create a Lambda function in programming language of your choice. This is where you will implement your custom validation / workflow logic.
  • Assign the lambda function to the Cognito event as a handler.

One of the biggest challenge I faced when I started out was to figure out:

  • Format and contents of the payload delivered to the Lambda function
  • Easy way to trigger these events without having to write a full fledged client application which can register / login the users

Trigger Payload

When Cognito invokes the Lambda function, it send out a JSON object containing details about the event. The structure and contents are described to an extent in AWS documentation.

But there is no easy to understand “SCHEMA” definition that you can use. Also since the Cognito user pool can be configured in multiple ways the payload will defer depending on the Cognito user pool configuration. One easy way to find out is by writing a simple lambda function.

Login to your AWS console and select AWS Lambda service. Hit the “Create function” button to create a new Lambda function. Give an appropriate name to the function and hit “Create function” again.

I am choosing to create this in Node JS but you can select any other language you are comfortable with to do this.

In the Function code section, you can type the following code and Save to create your Lambda function.

exports.handler = async (event) => {
console.log("event:" + JSON.stringify(event))
return event;
};

This function simply prints the payload that it received and simply returns the event object back to the Cognito service. Now every time the trigger is invoked, this function will be called and the function will print the payload it received. You can access the Lambda logs by accessing the Cloudwatch log group associated with this Lambda function.

Trigger Invocation

In order to invoke the trigger — It first needs to be configured as handler for an event. Let us configure the above Lambda function as pre-signup trigger.

Login to AWS console and navigate to the user pool configuration. Select the “triggers” from the left menu bar and then select the above function as handler for Pre Sign-up event.

Hit “Save changes” to save the configuration change. Now every time someone tries to sign-up using the user pool, the above function will be called. We will look at how you can invoke the sign-up API which will result in the above function being called.

Calling SignUp API (Using Postman / cURL)

As with all AWS services, Cognito service also has a well defined APIs that can be invoked over HTTP. Complete list of all APIs offered by Cognito identity service is available here.

We are interested in the “SignUp” action of the above API. So here is how we can invoke the SignUp function:

URL: https://cognito-idp.<<Region of user pool>>.amazonaws.com/

Here is a page that lists URL for all of the services of AWS

HTTP Method: POST

Headers:

Content-Type: application/x-amz-json-1.1X-AMZ-TARGET: AWSCognitoIdentityProviderService.SignUp

Payload:

{
“ClientId”: “<<Client ID of the Client associated with the user pool>>”,
“Password”: “<<Password of the user signing-up>>”,
“UserAttributes”: [
{
“Name”: “email”,
“Value”: “<<email of the user signing-up>>”
}
],
“SecretHash”: “<<Secret has calculated as shown below>>”,
“Username”: “<<User name of the user signing-up — This depends on how the user pool is configured>>”
}

Secret Hash:

HMAC Key = Client Secret of the Client associated with the user poolData = Concatenation of “Username” specified in payload + Client ID of the Client associated with the user poolSecretHash = BASE64 ( HMAC SHA256 ( user name + client id ) using Key = ( Client Secret of the Client associated with the user pool ) )

Sample calculation shown above. Depending on how the user pool is configured, you will get appropriate response. For user pool which is setup to use email as user name and mandate verified email, you will receive a response similar to following:

{
“CodeDeliveryDetails”: {
“AttributeName”: “email”,
“DeliveryMedium”: “EMAIL”,
“Destination”: “f***@m***.com”
},
“UserConfirmed”: false,
“UserSub”: “d61de885–3f94–4f97-aac5-c1025638ddf8”
}

The above response indicates that a code to verify the email has nee sent to the email specified by the user.

You can now access the CloudWatch logs of the function to see what payload was delivered to your Lambda function:

Enhancing Pre Sign-Up Trigger

Our pre sign-up lambda is not doing much right now. Let us enhance it a bit to see how you can use the power of Lambda to do interesting stuff and either allow or prevent sign-up.

In our Lambda function we are simply return the same event object back to the caller (which is the Cognito service). Returning the same event object back to Cognito allows Cognito to proceed with the next steps. If you want Cognito to prevent sign up — You can throw an exception back to the caller.

Change the Lambda code as follows:

exports.handler = async (event, context, callback) => {
console.log(“event:” + JSON.stringify(event))
if (event.request.userAttributes.email.includes(“@mailinator.com”))
{
throw “You can not sign-up using disposable email address”;
}
return event;
};

Now try to perform signup using an email address ending with “@maininator.com” and you will see following error returned by the Cognito service:

{
“__type”: “UserLambdaValidationException”,
“message”: “PreSignUp failed with error You can not sign-up using disposable email address.”
}

You can use the approach defined above with all the triggers supported by Cognito. Using Postman or cURL as shown above is the fastest way to invoke any AWS service without having to write a client application. In the next section, we will see how we can integrate Cognito with native Android application.

Cognito Integration With Native Android Application

There are two ways you can integrate AWS Cognito with native Android application.

  • AWS Mobile SDK for Android — This is now no longer the recommended approach as Amplify SDK offers same functionality with a much cleaner interface.
  • Amplify Android SDK — This is the latest SDK from AWS and is now the recommended way to integrate AWS services with native Android application. Amplify uses AWS Mobile SDK internally and provides a very easy to use interface compared to the one offered by AWS Mobile SDK.

At the core of the integration is the configuration file which contains details of the AWS resources (Cognito user pool, Cognito identity pool, S3 bucket) that you want your Android application to access. You can use the Amplify CLI to generate the configuration file. One drawback of the Amplify CLI is that it is unable to import existing AWS resources. So if you do not have any AWS resource defined then it makes sense to use Amplify CLI which will also generate all required AWS resources (e.g. Cognito user pool, Cognito identity pool, Social connector etc.). However of you have already configured these resources and want to just use those — You have to create the configuration yourself.

AWS Mobile SDK for Android

The configuration file used by the SDK is configured as a “raw” resource in the Android application. The default name of the configuration file is “awsconfiguration.json”. Typically you will have multiple environments (Dev, QA, Staging, Production) and want to use different user pools, s3 buckets etc. for each environment. There are two ways of achieving this.

Option 1 (The tedious way!):

Add environment specific configuration file e.g. awsconfiguration_qa.json, awsconfiguration_prod.json. Use appropriate configuration file using the “build configuration” feature of Android build gradle file. Here is how you can do it.

Place both the configuration files under the “raw” resource folder. You can then load the appropriate resources as follows:

The initialised AWSMobileClient instance can then be used for accessing the various AWS services including Cognito.

Following is how you can call the signup function:

AWSMobileClient.getInstance().signUp(userEmail, userPassword, attributes, null, this);

Option 2 (The easier way!):

The AWS configuration file is capable of storing “named” configurations. So you can create just one configuration file and created “named” configuration blocks. Here is how to do it.

The AWS configuration file with named configuration blocks:

Save the above file as awsconfiguration.json in the “raw” resource folder of the Android application. Define the names profiles in the build configuration using application’s grade file as follows:

buildTypes {
qa {
buildConfigField “String”, “AWS_CONFIG_KEY”, “\”AWS_QA\””

}
release {
buildConfigField “String”, “AWS_CONFIG_KEY”, “\”AWS_PROD\””

}
}

You can the use the following code to to initialise AWS Mobile client.

AWSConfiguration configuration = new AWSConfiguration(getApplicationContext(), R.raw.awsconfiguration,BuildConfig.AWS_CONFIG_KEY);AWSMobileClient.getInstance().initialize(getApplicationContext(), configuration, this);

Amplify Android SDK

The Amplify takes a slightly different approach to storing configuration information. It does not offer “named” configuration as shown above. So the only option is to create configuration file specific to the environment e.g. create amplifyconfiguration_qa.json to store QA environment configuration.

Define the build type in the build configuration using application’s grade file as follows:

buildTypes { qa {   buildConfigField “String”, “AMPLIFY_CONFIGURATION_TYPE”, “\”QA\””} release {   buildConfigField “String”, “AMPLIFY_CONFIGURATION_TYPE”, “\”PROD\””}}

Within your custom application class’s onCreate() method, use the following code to initialise Amplify SDK with appropriate configuration:

You can then use the following API to call the sign-up function of Cognito service.

// Initiate signupAuthSignUpOptions authSignUpOptions = AuthSignUpOptions.builder().userAttribute(AuthUserAttributeKey.email(), email).build();Amplify.Auth.signUp(email, password, authSignUpOptions, this, this);

--

--

Sanjay Dandekar

Developer, Home Cook, Amateur Photographer - In that order!.