AWS Mobile Blog

Tracking and Remembering Devices Using Amazon Cognito Your User Pools

by Jeff Bailey | on | | Comments

Introduction

With the general availability launch of Amazon Cognito Your User Pools, we introduced a new feature that enables device tracking and remembering. This feature provides insight into the usage of your app’s users and reduces the friction associated with multi-factor authentication (MFA). This blog post provides an overview of the feature, identifies the primary use cases, and describes how to set up the feature for your application.

 

Use cases

First, let’s take a look at some of the primary use cases for device remembering. The following examples are not exhaustive, but we use them in this blog post to illustrate the functionality.

This feature enables developers to remember the devices on which end users sign in to their application. You can see the remembered devices and associated metadata through the console and by using the ListDevices and GetDevice APIs. In addition, you can build custom functionality using the notion of remembered devices. For example, with a content distribution application (e.g., video streaming), you can limit the number of devices from which an end user can stream their content.

This feature works together with MFA to reduce some of the friction end users experience when using MFA. If SMS-based MFA is enabled for an Amazon Cognito user pool, end users must input a security code received via SMS during every sign-in in addition to entering their password. This increases security but comes at the expense of user experience, especially if users must get and enter a code for every sign-in. By using the new device remembering feature, a remembered device can serve in place of the security code delivered via SMS as a second factor of authentication. This suppresses the second authentication challenge from remembered devices and thus reduces the friction users experience with MFA.


Console setup

The following image shows how you can enable device remembering from the Amazon Cognito console.


 

The specifics of these configurations shown above can be made clearer by going over some terminology first.
 

Tracked

When devices are tracked, a set of device credentials consisting of a key and secret key pair is assigned to every device. You can view all tracked devices for a specific user from the Amazon Cognito console device browser, which you can view by choosing a user from the Users panel. In addition, you can see some metadata (whether it is remembered, time it began being tracked, last authenticated time, etc.) associated with the device and its usage.
 

Remembered

Remembered devices are also tracked. During user authentication, the key and secret pair assigned to a remembered device is used to authenticate the device to verify that it is the same device that the user previously used to sign in to the application. APIs to see remembered devices have been added to new releases of the Android, iOS, and JavaScript SDKs. You can also see remembered devices from the Amazon Cognito console.
 

Not Remembered

A not-remembered device is the flipside of being remembered, though the device is still tracked. The device is treated as if it was never used during the user authentication flow. This means that the device credentials are not used to authenticate the device. The new APIs in the AWS Mobile SDK do not expose these devices, but you can see them in the Amazon Cognito console.

 

Now, let’s go over the first configuration setting: Do you want to remember devices?
 

No (default) – By selecting this option, devices are neither remembered nor tracked.
 

Always – By selecting this option, every device used by your application’s users is remembered.
 

User Opt-In – By selecting this option, your user’s device is remembered only if that user opts to remember the device.  This configuration option enables your users to decide whether your application should remember the devices they use to sign in, though keep in mind that all devices are tracked regardless. This is a particularly useful option for a scenario where a higher security level is required but the user may sign in from a shared device; for example, if a user signs in to a banking application from a public computer at a library. In such a scenario, the user requires the option to decide whether their device is to be remembered.

The second configuration appears if you selected either Always or User Opt-In for the first configuration. It enables your application to use a remembered device as a second factor of authentication and thus suppresses the SMS-based challenge in the MFA flow. This feature works together with MFA and requires MFA to be enabled for the user pool. The device must first become remembered before it can be used to suppress the SMS-based challenge, so the first time a user signs in with a new device, the user must complete the SMS challenge; subsequently, the user does not need to complete the SMS challenge.

 

A deeper dive on device identification

As described previously, the device is identified and authenticated with a key and secret key credentials pair.

The path to getting these credentials is as follows:

1)      Every time the user signs in with a new device, the client is given the device key at the end of a successful authentication event.

2)      From this key, the client creates a secret, using the secure remote password (SRP) protocol, and generates a salt and password verifier.

3)      With that salt, verifier, and the key it was originally given, the client calls the ConfirmDevice API remotely. It is only then that Amazon Cognito begins tracking this device. During this entire flow, the secret remains on only the physical device.

The AWS Mobile SDKs for Android, JavaScript, and iOS support this flow implicitly – you do not need to do anything to make device confirmation work. It happens in the background of user authentication.

If you choose to have your users’ devices always remembered, then confirming the device marks it as remembered and begins tracking. If users must opt in to remember a device, confirming begins tracking the device as a not-remembered device. The response the client gets from that call indicates to the client that it must ask users if they want to remember the device. Each SDK can take a callback during the authentication call, which defines how users are asked if they want to remember the device. This authentication call consumes the UpdateDeviceStatus API. That API can be called any time to update the status as needed. The mobile SDKs use convenience wrappers around this method to make the calls more intuitive.
 

Android:

	// Create a callback handler to remember the device
	GenericHandler changeDeviceSettingsHandler = new GenericHandler() {
	    @Override
	    public void onSuccess() {
	        // Device status successfully changed
	    }

	    @Override
	    public void onFailure(Exception exception) {
	        // Probe exception for the cause of the failure
	    }
	};

	// To remember the device
	device.rememberThisDevice(changeDeviceSettingsHandler);
	// To not remember the device
	device.doNotRememberThisDevice(changeDeviceSettingsHandler);

 

iOS:


	// To remember a device
	[self.user updateDeviceStatus:YES] continueWithSuccessBlock:^id _Nullable(AWSTask<AWSCognitoIdentityUserUpdateDeviceStatusResponse*> * _Nonnull task) {
	    //Do something with task result here
	    return nil;
	}];

	// To not remember a device.
	[self.user updateDeviceStatus:NO] continueWithSuccessBlock:^id _Nullable(AWSTask<AWSCognitoIdentityUserUpdateDeviceStatusResponse*> * _Nonnull task) {
	    //Do something with task result here
	    return nil;
	}];

JavaScript:

	cognitoUser.setDeviceStatusRemembered({
	    onSuccess: function (result) {
	        console.log('call result: ' + result);
	    },

	    onFailure: function(err) {
	        alert(err);
	    }
	});

	cognitoUser.setDeviceStatusNotRemembered({
	    onSuccess: function (result) {
	        console.log('call result: ' + result);
	    },

	    onFailure: function(err) {
	        alert(err);
	    }
	});

The credentials provided to the device should be persistent and will be stored by the AWS Mobile SDKs, so any logic you build on top of the device key as an identifier can assume it will not change. The only way it could change is if the user wipes the device’s storage, if the user uninstalls the application, or if the ForgetDevice API is called for a device (this API removes all tracked devices for a user). If any of those occur, the next authentication treats the device as if it had never been used before.

If a device is remembered, the device credentials are authenticated as part of the user authentication flow, using the SRP protocol. This authentication verifies that the key the service was provided is one that it generated itself, from the user it was generated for, and from the device it was given to.

A successful authentication by a user generates a set of tokens – an ID token, a short-lived access token, and a longer-lived refresh token. The access token only works for one hour, but a new one can be retrieved with the refresh token, as long as the refresh token is valid. With device tracking, these tokens are linked to a single device. If a refresh token is used on any other device, the call fails. In a scenario where, for example, a device is stolen, the ForgetDevice API can be used to forget that specific device, and as a result, all future calls to revalidate that device’s refresh tokens will fail.
 

How can you use this device identifier?

There are a few APIs exposed on the client SDKs that enable you to see the remembered devices for the user that is currently signed in. Users must be signed in to view their devices because all APIs are authenticated with an access token.

First, if you want to get a single device’s metadata, you call GetDevice as shown in the following examples.
 

Android:

	DevicesHandler getDeviceHandler = new DevicesHandler() {
	    @Override
	    public void onSuccess(List<CognitoDevice> devices) {
	        // This list will have one device in it, and will update the
	        // current device (currDevice)
	    }

	    @Override
	    public void onFailure(Exception exception) {
	        // Check exception for the cause of failure.
	    }
	};
	currDevice.getDevice(getDeviceHandler);

	// If you want to get the current device’s metadata
	user.thisDevice();


iOS:

	[self.user getDevice] continueWithSuccessBlock:^id _Nullable(AWSTask<AWSCognitoIdentityUserGetDeviceResponse *> * _Nonnull task) {
	    //Do something with task result here
	    return nil;
	}];

JavaScript:

	cognitoUser.getDevice({
	    onSuccess: function (result) {
	        console.log('call result: ' + result);
	    },

	    onFailure: function(err) {
	        alert(err);
	    }
	});

If you want to list all remembered devices for a user, you call the ListDevics API.

Android:

	DevicesHandler listDevicesHandler = new DevicesHandler() {
	    @Override
	    public void onSuccess(List<CognitoDevice> devices) {
	        // devices contains the list of all devices that are remembered
	    }

	    @Override
	    public void onFailure(Exception exception) {
	       // Check exception for the cause of failure.
	    }
	};

	user.listDevices(listDevicesHandler);

iOS:

	//The first parameter is page size, second is paginationToken from previous call
	[self.user listDevices:10 paginationToken:nil] continueWithSuccessBlock:^id _Nullable(AWSTask<AWSCognitoIdentityUserListDevicesResponse *> * _Nonnull task) {
	    //Do something with task result here
	    return nil;
	}];

JavaScript:

	cognitoUser.listDevices(limit, paginationToken, {
	    onSuccess: function (result) {
	        console.log('call result: ' + result);
	     },

	    onFailure: function(err) {
	        alert(err);
	    }
	});


If a user wants to stop tracking a device that is currently remembered, you call the ForgetDevice API.
 

Android:

	DevicesHandler forgetDeviceHandler = new DevicesHandler() {
	    @Override
	    public void onSuccess(List<CognitoDevice> devices) {
	        // this will forget whatever currDevice is. If it’s the
	        // physical device, it will also clear the local tokens.
	    }

	    @Override
	    public void onFailure(Exception exception) {
	        // Check exception for the cause of failure.
	    }
	};

	currDevice.forgetDevice(forgetDeviceHandler);

iOS:

	[self.user forgetDevice] continueWithSuccessBlock:^id _Nullable(AWSTask* _Nonnull task) {
	    //Do something with task result here
	    return nil;
	}];

JavaScript:

	cognitoUser.forgetDevice({
	    onSuccess: function (result) {
	         console.log('call result: ' + result);
	     },

	     onFailure: function(err) {
	         alert(err);
	     }
	});

From the console, if you search for a user or choose a user that is on the user list, you can see every device tracked for that user with its key, name, the IP used when last authenticated, whether or not the device is remembered, the AWS SDK it used, and the time it last authenticated.

 

 

Limiting devices per user

You can use this feature if, for example, you are developing a content distribution app and want to limit the number of devices that users can connect to their account.

To enable this use case, we have included an extra parameter in our inputs to our post-authentication AWS Lambda function: newDeviceUsed. It is a Boolean flag that is only true if you have device remembering turned on and if the device being used to authenticate is a new device.

With this small addition, you can set a maximum for the number of devices that can be linked to a user’s account. Within your post-authentication Lambda hook, you can call the AdminListDevices API to count the number of devices currently linked if the newDeviceUsed flag is set to true. If it is over your determined limit, you fail the call, which then fails the authentication.

 

Conclusion

As you can see, integrating device remembering into your mobile and web applications is straightforward using the AWS Mobile SDKs. Device remembering enables you to create an experience that is designed to provide security and a user-friendly experience for your users. We’d love to hear how you plan to use this feature in your applications, so feel free to leave a comment to share other uses for this feature.

If you encounter issues or have comments or questions, please leave a comment, visit our forums, or post on Stack Overflow

 

AWS Mobile Hub Helper Code for iOS is now Available on GitHub

by Karthik Saligrama | on | | Comments

In March 2016 we announced Swift support for AWS Mobile Hub. To make it easier for developers to integrate, we moved the code into a single framework. A side effect of that move was that the source code was no longer included in the project download. Customers could not update the source code of the framework if they wanted to extend the features AWS Mobile Hub already supports.

Last week, we open-sourced the AWS Mobile Hub helper code on GitHub. You can find the source here and the API reference documentation here. To download the binary (framework) please continue to use the AWS Mobile Hub Console. From your AWS Mobile Hub project, choose Build, choose iOS Obj-C or iOS Swift, and download the source package.

Building the Source

If you decide to make changes to the source code, you can build the framework using the scripts located here. Here are the steps:

1) Clone the repo https://github.com/aws/aws-mobilehub-helper-ios.git in any directory using:

	git clone https://github.com/aws/aws-mobilehub-helper-ios.git

2) Change the directory to cd aws-mobilehub-helper-ios/. The Source code uses CocoaPods for dependency management. You can install CocoaPods by using sudo gem install cocoapods. Run Pod Install from the root directory. This installs all of the dependencies for the project. After the installation is complete, you should get the following message:

 

	Analyzing dependencies Downloading dependencies
	Installing AWSCognito (2.4.7)
	Installing AWSCore (2.4.7)
	Installing AWSLambda (2.4.7)
	Installing AWSS3 (2.4.7)
	Installing AWSSNS (2.4.7)
	........
	Integrating client project Sending stats Pod installation complete!
	There are 7 dependencies from the Podfile and 14 total pods installed.

3)You can now run the following scripts from your project root directory using

	./Scripts/GenerateHelperFramework.sh

This generates a static framework in the <ProjectRoot>/builtframework/framework and you can include it in your project. If you decide to update the version of any dependency, make sure that the framework file for that dependency is also updated in your project.

Contributing

You can contribute to AWS Mobile Hub helper code by submitting pull requests on our GitHub repo. However, there are a few restrictions. If you would like to add an entirely new feature, submit feedback on the console for the new feature, using the feedback link on the bottom left corner of the console. Everything else, like minor patches and bug fixes, can be via pull requests.

Find, Try, and Purchase Mobile Software in AWS Marketplace

by Rob Lipschutz | on | | Comments

AWS Marketplace has worked with ISVs throughout our ecosystem to introduce new mobile software in AWS Marketplace throughout the past several months. This is software that helps enterprises, mid-market companies, and startups create and manage mobile apps for their customers or employees.

The Mobile Factory

With more than 30 new mobile products from over 20 different ISVs categorized by the three phases of the mobile application development process: Build & Launch, Secure & Integrate, and Launch & Manage, you’ll find software options for every phase of your project. Check out the AWS Marketplace Mobile Factory Web Page where products in each stage are highlighted with links to their product listing page in AWS Marketplace. You’ll find products in easy-to-deploy Amazon Machine Images (AMIs) and SaaS, paid and open source, and products suitable for startups or large enterprises. We call this the AWS Marketplace Mobile Factory because AWS Marketplace provides the components to help you construct and launch Mobile Apps more efficiently.

AWS Marketplace Mobile Factory: Mobile Software for the Entire Mobile App Lifecycle

Product Highlights

For example, you can find enterprise mobile frameworks from Kony to build mobile apps that connect to back-end systems and innovative stacks from Bitfusion for integrating video, deep learning, and imaging into mobile apps by off-loading these compute intensive tasks to AWS compute services. You can find open source software such as Phone Gap, Cordova, and Ionic from GlobalSolutions available as a complete stack that installs in 2 clicks into your AWS environment. For testing before and after deployment, you’ll find HPE’s suite of products including StormRunner Load and AppPulse Mobile. For securing Apps, Proofpoint makes its Mobile Defense product available in AWS Marketplace. You’ll also find listings for Apigee Edge, a sophisticated API management system, and a number of analytics and prediction tools like New Relic, Arimo, and AppDynamics.

AWS Marketplace Web site

Integration with AWS

AWS Marketplace software extends and complements AWS Mobile Services and AWS services. For example, Bitfusion’s products can leverage the specialized g2.2xlarge and g2.8xlarge Amazon EC2 instances for its media tasks and Apigee Edge works together with AWS Lambda. All products run on EC2 instances within a customer’s AWS infrastructure or as a SaaS offering leveraging AWS infrastructure.

Given your specific requirements and the myriad of products available, it’s helpful to have a place where you can quickly try software that runs on AWS. Flexible pricing for many listings includes pay as you go hourly pricing, and many listings have free trials (AWS usage fees may apply) to get started. These pricing options give AWS customers the opportunity to evaluate products more cost-effectively. This flexibility and reversibility makes AWS Marketplace a great place to experiment for Agile developers.

Please send your questions or suggestions for mobile software in AWS Marketplace to aws-marketplace-mobile@amazon.com. If you are an ISV with a mobile product, check out the informative blog post “How to List Your Product in AWS Marketplace” written by Suney Sharma on the AWS Partner Network (APN) blog.

Amazon Mobile Analytics Auto Export to Amazon S3 – Feature Update

by Georgie Mathews | on | | Comments

Amazon Mobile Analytics Auto Export to Amazon S3 accumulates and exports events sent to the Amazon Mobile Analytics service from your mobile and web applications into your own Amazon S3 bucket, within one hour from when we receive the event. This allows you to access the full data being recorded by your application to perform additional detailed analysis in addition to the analytics automatically produced in the Amazon Mobile Analytics console, such as Daily Active Users (DAU), Monthly Active Users (MAU), Average Revenue per Daily Active User and other out-of-the-box and custom metrics.

 

What’s New?

We’re making feature improvements to how data is processed after we receive it. Before these changes, files were written three times to ensure all data are published to your Amazon S3 bucket. Going forward, files will generally be written only a single time which will reduce the inbound bandwidth to your Amazon S3 bucket. This will also reduce the average amount of time it takes for Auto Export to Amazon S3 to deliver files into your Amazon S3 bucket. If you are an existing customer of this feature, make sure to read our forum announcement regarding a small change to how the files are named. Please note that the structure of the files is not changing and there are no changes in how the Auto Export to Amazon Redshift feature works.

 

How do I start using it?

If you’re already using Amazon Mobile Analytics but have not enabled Auto Export to Amazon S3, log in to AWS and navigate to the Amazon Mobile Analytics console to enable the feature for one or more of your applications.  You can also choose to have your data exported to Amazon Redshift while you’re there. You can find the steps to enable Auto Export here.

If you haven’t integrated Amazon Mobile Analytics into any of your applications yet, check out our User Guide and get your first Android, iOS, JavaScript, Unity, Xamarin, or custom application integrated today!

 

FAQs

1. Which development platforms do you support?

We have SDKs to integrate with the following platforms: iOS, Android, JavaScript, Unity, and Xamarin. We also support REST API access.

 

2. How do I export data collected from before I enabled Auto Export?

At this time, the Amazon Mobile Analytics Auto Export to Amazon S3 feature only supports data export on a forward basis.  Data collected prior to enabling this feature is not available for export to Amazon S3 or Amazon Redshift, however, it is used to calculate the analytic metrics that are accessible via the Amazon Mobile Analytics console.

 

3. How are the Amazon S3 file names generated by Auto Export to Amazon S3 changing?

The current file name pattern is as follows:

<bucket-name>/awsma/events/<appId>/<YYYY>/<MM>/<DD>/<hh>/<appId>-<mm>-part-<partNum>.gz

After the change, the file name pattern will be:

<bucket-name>/awsma/events/<appId>/<YYYY>/<MM>/<DD>/<hh>/<appId>-<mm>-part-<partNum>-<hexCode>.gz


 

Test User Interfaces in iOS Apps with XCTest UI and AWS Device Farm

by Asha Chakrabarty | on | | Comments

With AWS Device Farm, you can quickly start testing your Android, iOS, and FireOS apps on real devices in the AWS Cloud. Choose to start an interactive session with a device or run automated tests on many devices at once. AWS Device Farm will provide the results of your tests including pass/fail status, logs, performance metrics, screenshots, and videos.

Introduction to XCTest UI

As of Xcode 7, you can access UI testing capabilities (XCUI) integrated into the Xcode IDE. This functionality allows you to find and interact with UI elements in your app and confirm their properties and states. A few new features make it possible for you to programmatically test and exercise the UI of an iOS app with:

  • New Xcode target type for UI tests:

To set up a UI test in Xcode, you create an Xcode target with the iOS UI Testing Bundle template. The Xcode target type fulfills the special requirements required by the UI tests, including launching a proxy for the application in a separate process and providing accessibility permissions.

  • UI testing APIs include three key classes:

    • XCUIElementQuery: Every UI element is backed by the ability to query its properties. For this reason, each XCUIElement must be unique.
    • XCUIElement: A proxy object for a UI element that is represented as types (for example, a cell or a button).
    • XCUIApplication: An instantiation of your application object that forms the origin for finding UI elements.
  • UI recording:

This allows you to record interactions with your app’s user interface. Xcode will transform these interactions into source code that can be included in your existing tests or to create new tests. 

AWS Device Farm now allows you to run the UI Testing feature incorporated in Xcode 7 on real devices in the AWS Cloud. In this post, we will walk you through how to create an XCTest UI test, package it for testing on AWS Device Farm, schedule a run, and view test results from real devices in the cloud.

Prerequisites

  • You’ll find the sample iOS app used in this post on AWS Labs on GitHub.
  • UI Testing was introduced in Xcode 7 and iOS 9, so be sure to update accordingly.
  • iOS devices must be enabled for development and connected to a host running Xcode.
  • It is assumed that you have created the .ipa file for the sample iOS app before you schedule a run in AWS Device Farm.

Step 1: Create a UI Test for the AWS Sample iOS App

After you have downloaded and opened the sample app in Xcode, build the project. After the build is successful, you will create a new a target type for the UI tests.

Your project navigator should look like the following:

With UI testing, you can record interactions within your app and Xcode will write the code required to re-enact those interactions in your test. You will still need to use XCTAssert to add your test assertions. You can record interactions with your UI by pressing the record button (the small red dot at the bottom left corner of the editor pane).

Copy the following UI test code to your AWSDeviceFarmiOSReferenceAppUITests.m implementation file.

#import 

@interface AWSDeviceFarmiOSReferenceAppUITests : XCTestCase

@end

@implementation AWSDeviceFarmiOSReferenceAppUITests

- (void)setUp {
    
    [super setUp];
    self.continueAfterFailure = NO;
    [[[XCUIApplication alloc] init] launch];
    
}


- (void)tearDown {
    
    [super tearDown];
}


- (void)testNativeInput {
    
    XCUIApplication *app = [[XCUIApplication alloc] init];
    XCUIElementQuery *tabBarsQuery = app.tabBars;
    [tabBarsQuery.buttons[@"Native"] tap];
    
    XCUIElementQuery *collectionViewsQuery = app.collectionViews;
    [collectionViewsQuery.staticTexts[@"Table of elements"] tap];
    [app.navigationBars[@"ElementsTableView"].buttons[@"Menu"] tap];
    [collectionViewsQuery.staticTexts[@"Scrolling View"] tap];
    [app.navigationBars[@"Scrolling View"].buttons[@"Menu"] tap];
    [tabBarsQuery.buttons[@"Home"] tap];
    
}


- (void)testNestedView {
    
    
    XCUIApplication *app = [[XCUIApplication alloc] init];
    XCUIElementQuery *tabBarsQuery = app.tabBars;
    [tabBarsQuery.buttons[@"More"] tap];
    [app.staticTexts[@"Nested"] tap];
    
    XCUIElement *moreNavigationBar = app.navigationBars[@"More"];
    XCUIElement *nextButton = moreNavigationBar.buttons[@"Next"];
    [nextButton tap];
    [nextButton tap];
    [nextButton tap];
    
    XCUIElement *backButton = [[[moreNavigationBar childrenMatchingType:XCUIElementTypeButton] matchingIdentifier:@"Back"] elementBoundByIndex:0];
    [backButton tap];
    [backButton tap];
    [backButton tap];
    [moreNavigationBar.buttons[@"More"] tap];
    [tabBarsQuery.buttons[@"Home"] tap];
    
}


- (void)testAlertControl {
    
    
    XCUIApplication *app = [[XCUIApplication alloc] init];
    XCUIElementQuery *tabBarsQuery = app.tabBars;
    [tabBarsQuery.buttons[@"More"] tap];
    [app.staticTexts[@"Alerts"] tap];
    [app.buttons[@"Modal"] tap];
    [app.buttons[@"OK"] tap];
    [app.buttons[@"Alert"] tap];
    [app.alerts[@"Alert"].collectionViews.buttons[@"OK"] tap];
    [app.navigationBars[@"More"].buttons[@"More"] tap];
    [tabBarsQuery.buttons[@"Native"] tap];
    [app.collectionViews.staticTexts[@"Image Gallery"] tap];
        
}

@end

Before packaging your test for AWS Device Farm, be sure to build the project with Xcode. Use the Product/Build for Running option select an iOS device. Keep in mind that the Build Active Architecture setting for your app and your UI test targets should be the same.

Step 2: Package Your Test for AWS Device Farm

When packaging your test for upload to AWS Device Farm, make sure your iOS XCTest UI Runner test runner bundle is contained in a correctly formatted .ipa file. For more information, see the AWS Device Farm documentation. You can also view the creation of an .ipa file on AWS Labs on GitHub.

Make sure that you package your test in the Payload folder under debug-iphoneos as shown here. In the following screenshot, we renamed the resulting zip file of the Payload folder to UITest.ipa for easier file management.

Step 3: Schedule a Run in AWS Device Farm

Sign in to the AWS Device Farm console and create a run under a new or existing project. Upload the .ipa file of the sample app.

In the next step, you will choose XCTest UI as the test type and upload the .ipa file you created in step 2.

Select the devices on which you’d like to test. If you like, you can create a new device pool for your test run to reuse in subsequent runs.

Finally, review and start the test run.

Step 4: View XCTest UI Test Results

When your test run is complete, you will see the test results summary.

Choose a device to examine its test suites. Here we are reviewing the test suite results for an Apple iPhone 5s and Apple iPhone 6 device.

The results for each device will contain screenshots, video recordings, performance data, and log files that can be downloaded for further review.

Conclusion

We are always happy to hear your feedback. Feel free to leave your feedback, including questions, in the comments or on our developer forum.

Happy testing!

Introducing Worldwide SMS Messaging

by Arjun Cholkar | on | | Comments

Introduction

Earlier this week, Amazon SNS released worldwide SMS delivery and made it available in six AWS Regions. Worldwide SMS delivery means that you can now send SMS text messages directly to mobile phone numbers in more than 200 countries. Along with this expansion, SNS also enabled default “opt-in” of recipient phone numbers. This creates more possibilities for SMS messages, such as those needed for multi-factor authentication (MFA) or one-time passcodes.

In this blog post, we will cover notable changes from the previous SMS offering and highlight the new features. We’ll also describe SMS account-level configuration, show how to set up delivery status, and describe the new SMS API calls and message attributes.

What’s new with worldwide SMS delivery?

The following table shows the previous SMS offering and the features and options that are now available with the new worldwide SMS delivery from Amazon SNS.

SMS (former capability)

Worldwide SMS (new)

US-based phone numbers only.

Global support of phone numbers in 200+ countries. The full list of supported countries can be found here.

Each phone number required “opt-in” from recipient prior to the developer sending messages.

No opt-in required.

SMS delivery available in the us-east-1 Region only.

Available in the following AWS Regions:

us-east-1: US East (N. Virginia)

us-west-2: US West (Oregon)

eu-west-1: EU (Ireland)

ap-northeast-1: Asia Pacific (Tokyo)

ap-southeast-1: Asia Pacific (Singapore)

ap-southeast-2: Asia Pacific (Sydney)

All US-based phone numbers required a subscription to an SNS topic with a display name; developers published to the topic.

Developers can now directly publish to a phone number without requiring subscriptions to an SNS topic. They can also subscribe the phone number to an SNS topic and publish to the topic.

N/A

Manage opted-out phone numbers in the AWS Management Console, API, CLI or SDK. More details here.

Delivery status available for application, Lambda, HTTP, and SQS protocols only.

SMS has its own delivery status that can be configured per region. Delivery status can be enabled in the AWS Management Console, the AWS CLI, or SDK.

N/A

Optionally set account (maximum spend per month) and message level (maximum spend per message) spend limits.

N/A

Optional Daily SMS Usage Reports (in CSV) for successful and failed SMS deliveries. Set-up instructions can be found here.

All SMS text messages were delivered from the same Amazon short code: 30304.

Amazon SNS uses a pool of long codes or short codes to send SMS notifications. Users will no longer receive messages from the 30304 short code. Additionally SNS sends messages from an AWS account to a phone number from the same long or short code (this is called the Sticky Sender ID).

N/A

With worldwide SMS delivery, developers can choose between transactional or promotional message types to optimize for high delivery success or cost savings. More details can be found here.

Notable Changes to existing SMS subscriptions

  • All SMS phone numbers actively subscribed to an SNS topic will see a new long code or short code when a new message is delivered after June 28. After the new long/short code is established, recurring messages should come from the same code (though this is not guaranteed). This feature is called Sticky Sender ID.
  • When a user sends “STOP”, the phone number will not be unsubscribed from the SNS topic. Instead, the phone number is added to the new “opted out” list that you manage. The SMS phone number that is subscribed to the SNS topic will still show subscribed even after a “STOP” request. However, if you publish to the SNS topic for which the opted out phone number exists, the delivery will fail and the failed message will be logged to Amazon CloudWatch Logs (if you enabled delivery status, of course).

SMS account-level configuration

You can set account-level text messaging preferences in the Amazon SNS console. In messaging preferences, you can specify account-level SMS message type, specify the spend limit, enable delivery status, provide a sender ID, and specify an Amazon S3 bucket to receive daily SMS delivery reports.

Here’s what the configuration screen looks like, including a description for each preference.

 

Delivery status

What is SNS delivery status?

SNS delivery status logs successful and unsuccessful SMS deliveries to Amazon CloudWatch Logs. Delivery status for SMS enables developers to collect logged data on successful and unsuccessful delivery attempts of their SMS messages to individual phone numbers. It also provides information about dwell times in Amazon SNS. Dwell times indicate: 1) how long it takes to deliver the message to the destination carrier from the time the message was published to SNS, and 2) how long it takes for the recipient carrier to acknowledge delivery of that message from the time the message was published to SNS. In addition, the price per message is logged so that you can manage your SMS costs.

Note: Delivery status plays a very important role in troubleshooting SMS deliverability. If you have issues with deliverability, consider enabling and configuring delivery status so that you can quickly identify and troubleshoot the issue.

Enabling delivery status

You can enable the delivery status feature for SMS with the Amazon SNS console, the AWS CLI, or the AWS SDKs. The simplest way is to enable delivery status using the SNS console, as described next.

Enabling SMS delivery status using the Amazon SNS console

Step 1: Log in to the Amazon SNS console.

Step 2: Navigate to Text Messaging (SMS).

Step 3: Select Create IAM role next to Default IAM role for success feedback.

Step 4: On the SNS is requesting permission to use resources in your account page, choose Allow. This allows Amazon SNS to gain write access to Amazon CloudWatch Logs on your behalf.

escription: Allow Access

You should now see an IAM role as shown in the following example:

https://dmhnzl5mp9mj6.cloudfront.net/mobile_awsblog/images/sms-iam-role.png

Example CloudWatch Logs

Delivery status for SMS creates two CloudWatch Log Groups: Successful and Failed. The groups have the following format:

Successful: sns/<region>/<accountID>/DirectPublishToPhoneNumber

Failed: sns/<region>/<accountID>/DirectPublishToPhoneNumber/Failure

Note: If one of the groups does not exist, then no logs have been delivered to that group, or the IAM role assumed by SNS does not have permission to write to CloudWatch Logs.

Example log for successful SMS delivery

The delivery status log for a successful SMS delivery looks like the following example:

Example log for failed SMS delivery

The delivery status log for a failed SMS delivery looks like the following example:

New APIs for SMS worldwide delivery

Along with the announcement of worldwide SMS delivery, there are new API calls and attributes for setting SMS preferences and text message delivery.

SMS Account Preferences

The following are the new API calls for setting and getting account SMS preferences:

SetSMSAttributes

GetSMSAttributes

The following are the new SMS account attributes for setting and getting SMS account preferences:

DefaultSenderID, MonthlySpendLimit, DeliveryStatusIAMRoleDeliveryStatusSuccessSamplingRate, DefaultSMSType, and UsageReportS3Bucket.

For all SMS attributes, see SetSMSAttributes and GetSMSAttributes in the Amazon Simple Notification Service API reference.

Sending a message

You can send an SMS text message through the SNS console, CLI, or SDK. Remember, you can now publish directly to the individual phone number without going through an SNS topic or first waiting for the user to opt in.

Note: When you send an SMS message, specify the phone number using the E.164 format. E.164 is a standard for the phone number structure used for international telecommunication. Phone numbers that follow this format have a maximum of 15 digits. They are prefixed with the plus character (+) and the country code. For example, a U.S. phone number in E.164 format would appear as +1XXX5550100.

To send an SMS message by using one of AWS SDKs, use the same SNS Publish action in the SDK that corresponds to the Publish request in the Amazon SNS API.

To directly publish to a phone number, you’ll use the new phoneNumber and message parameters of the Publish API.

Java

.withMessage(message) // Specify the text message sent to the mobile phone.

.withPhoneNumber(phoneNumber) // Specify the phone number using the E.164 format.

.withMessageAttributes(smsAttributes) // Specify one or more of the three new MessageAttributes shown next.

JavaScript

Message, PhoneNumber, and additional MessageAttributes

var params = { "Message": value, "PhoneNumber": value }

The following are the new MessageAttributes for the Publish API:

AWS.SNS.SMS.SenderID  // (Optional) defaults to an SNS specified value, if not specified by the sender.

AWS.SNS.SMS.MaxPrice // (Optional) defaults to no max price.

AWS.SNS.SMS.SMSType // (Optional) defaults to "Transactional".

Tip: For Java, you will apply your attributes to the PublishRequest object of the Amazon SNS client.

Questions?

Check out the SMS FAQ here.

Resources

Amazon SNS Developer Guide

Amazon SNS SMS FAQ

Amazon SNS Forum

 

Happy text messaging!

Announcing SAML Support for Amazon Cognito

by Vinay Kushwaha | on | | Comments

Today, we are excited to announce support in Amazon Cognito for Security Assertion Markup Language (SAML) 2.0 authentication. SAML 2.0 is an XML-based open standard that is used to transfer authentication and authorization data between parties.

Now developers can sign in users through their own SAML identity providers and provide secure access through Amazon Cognito to their AWS resources, such as Amazon S3 and Amazon DynamoDB. 

With SAML support in Amazon Cognito, developers can also configure IAM roles to provide different permissions to different users and groups.

This post covers how you can configure SAML for Amazon Cognito to authenticate your users and assign them to specific AWS IAM roles. 

1. Configure your identity pool for a SAML provider

To configure your identity pool to support a SAML provider, choose the SAML tab in the Authentication provider section of the Amazon Cognito console. If you want to use an existing SAML provider, select the provider and save the changes for the identity pool.

To create a new SAML provider:

  • Choose the AWS IAM console link to go to the IAM console.
  • In the left pane, choose Identity Providers.
  • Choose Create Provider and then choose SAML in Provider Type.

  • Type a name for the SAML provider and choose the SAML metadata document file that you received from your SAML Identity Provider (IdP). This file typically includes the issuer’s name, expiration information, and a certificate that can be used to validate the SAML assertion response received from the IdP. For Microsoft Active Directory Federation Services (AD FS) you can download it from
    https://<yourservername>/FederationMetadata/2007-06/FederationMetadata.xml.
    For more information related to different SAML IdPs, see Integrating Third-Party SAML Solution Providers with AWS.

2. Create roles

Now you create roles that you want to associate with your end user.  Unlike other types of IdPs, when you use a SAML-based IdP with Cognito, you do not have to select authenticated role for your Cognito identity pool. However, you can choose Create new role next to the Authenticated role label in the Cognito console and allow it to create the correct trust relationship as a convenient way to start creating new IAM roles. You can further configure the role policy and trust policy by using the IAM Console. Refer to the Cognito developer guide to learn more about the role policy by using context keys available with Cognito. You can create as many roles as required by your application and configure your IdP to populate the role in SAML assertion response. The role received in the SAML assertion from your IdP will be used for vending the AWS credentials for your users.

The trust policy for your role used by Cognito will look similar to the following policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "us-east-1:12345678-identity-pool-id-123abc"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "authenticated"
        }
      }
    }
  ]
}

3. Configure your SAML IdP

After you create the SAML provider with AWS and setup your IAM roles, you can configure your SAML IdP to add relying party trust between your IdP and AWS.  Many IdPs allow you to specify a URL from which the IdP can read an XML document that contains relying party information and certificates. For AWS, you can use https://signin.aws.amazon.com/static/saml-metadata.xml

The next step is to configure the SAML assertion response from your IdP to populate the claims needed by AWS. For details on the claim configuration, see Configuring SAML Assertions for the Authentication Response. For instructions on how to configure many popular IdPs, see Integrating Third-Party SAML Solution Providers with AWS.

4. Customize Roles

Using SAML with Amazon Cognito Identity allows the IAM role to be customized for the end user.  You configure your organization’s IdP in a way that it maps users or groups in your organization to the IAM roles you want those users to assume. The exact steps for performing the mapping depend on what IdP you’re using. Essentially you will be configuring the claim attribute https://aws.amazon.com/SAML/Attributes/Role to get populated in the SAML assertion response. This attribute specifies one or more pairs of comma delimited roles and provider ARNs. These are the roles for which the user is allowed to get credentials.

If multiple roles are received in the SAML assertion response, the optional customRoleArn parameter should be populated while calling getCredentialsForIdentity.  The role received in the customRoleArn parameter will be used if it matches a role in the SAML assertion claim. Your app will need to provide the logic or prompt the user to choose a role if multiple roles are included in the SAML assertion.

The value of the https://aws.amazon.com/SAML/Attributes/RoleSessionName claim attribute is used for setting the role session name in the credentials vended by Amazon Cognito. Role session name is set to a generic value ‘CognitoIdentityCredentials’ by Amazon Cognito if your identity pool supports multiple SAML providers and multiple assertions are received in the logins map which have different values of RoleSessionName.

5. Authenticate users with SAML IdP and get SAML assertion

After you have set up your Amazon Cognito identity pool and SAML IdP, you are ready to authenticate the user against the SAML IdP and federate with Amazon Cognito. The SAML IdP issues a SAML assertion for the authenticated user. The base-64 encoded assertion response must be passed to Amazon Cognito as a value in the logins map. The key in this map will be the ARN of the SAML provider that you associated with your identity pool in step #1.

To federate with SAML based IdP, you will need to determine the URL which is being used to initiate the login. AWS federation utilizes IdP-initiated login. In ADFS 2.0 URL takes the form of https://<fqdn>/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices

The following describes how to extract the SAML assertion response from ADFS. 

  • Populate the IdP URL, username, and password in the following code. Connect to the IdP with NTLM authentication. This should return an HTML input form in the response, which will have a  SAML assertion as a value. The following example shows the Java code for this step.  For different SAML IdPs and platforms, this step might be different. Refer to the documentation from your SAML identity provider to learn how to get the SAML assertion response.
        String idpurl= "https://<fqdn>/adfs/ls/IdpInitiatedSignOn.aspx?loginToRp=urn:amazon:webservices";

        Authenticator.setDefault(new Authenticator() {
            @Override
            public PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(username, password.toCharArray());
            }
        });

        URL urlRequest = new URL(idpurl);
        HttpURLConnection conn = (HttpURLConnection) urlRequest.openConnection();
        conn.setDoOutput(true);
        conn.setDoInput(true);
        conn.setRequestMethod("GET");

        InputStream stream = conn.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(stream));

        StringBuilder response = new StringBuilder();
        String str;
        while ((str = reader.readLine()) != null) {
            response.append(str);
        }
        String html = response.toString();
  • After you receive the HTML response from the IdP, use the following example code to parse the response and extract the Base64-encoded SAML response from it. 
        DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document = builder.parse(new InputSource(new StringReader(html)));

        Element docElement = document.getDocumentElement();
        NodeList inputNodes = docElement.getElementsByTagName("input");
        if (inputNodes != null && inputNodes.getLength() > 0) {

            for (int i = 0; i <= inputNodes.getLength() - 1; i++)  {
                Node inputNode = inputNodes.item(i);
                NamedNodeMap inputNodeAttributes = inputNode.getAttributes();

                Map inputAttibuteMap = new HashMap();

                for (int j = 0; j <= inputNodeAttributes.getLength() - 1; j++) {
                    inputAttibuteMap.put(inputNodeAttributes.item(j).getNodeName(),
                            inputNodeAttributes.item(j).getNodeValue());
                }                
                if (inputAttibuteMap.get("name").equals("SAMLResponse")) {
                    // Base 64 encoded SAML Authentication response.
                    return inputAttibuteMap.get("value");
                }
            }
        }
     

6. Use the SAML assertion to call Amazon Cognito

The examples below show how to call Cognito with the SAML assertion on different platforms. Please note, only the Enhanced flow in Amazon Congito is supported with SAML based IdP.

Java:

    AmazonCognitoIdentity client = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials());
    // Get id request. This only needs to be executed the first time and the result should be cached.
    GetIdRequest idRequest = new GetIdRequest();
    idRequest.setAccountId("aws account id");
    idRequest.setIdentityPoolId("identity pool id");

    Map logins = new HashMap();
    logins.put("arn:aws:iam::aws account id:saml-provider/name", "base64 encoded assertion response");
    idRequest.setLogins(logins );
    GetIdResult idResp = client.getId(idRequest);
    String identityId = idResp.getIdentityId();

    // GetCredentialsForIdentity call.
    GetCredentialsForIdentityRequest credRequest = new GetCredentialsForIdentityRequest();
    request.setIdentityId(identityId);
    request.setLogins(logins);

    // If SAML assertion contains multiple roles, resolve the role by setting the custom role
    request.setCustomRoleArn("arn:aws:iam::aws account id:role/admin");

    GetCredentialsForIdentityResult credetialsResult = client.getCredentialsForIdentity(credRequest)

 

Android

If you are using the Android SDK you can populate the logins map with the SAML assertion as follows.

    Map logins = new HashMap();
    logins.put("arn:aws:iam::aws account id:saml-provider/name", "base64 encoded assertion response");
    // Now this should be set to CognitoCachingCredentialsProvider object.
    CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(context, identity pool id, region);
    credentialsProvider.setLogins(logins);
    // If SAML assertion contains multiple roles, resolve the role by setting the custom role
    credentialsProvider.setCustomRoleArn("arn:aws:iam::aws account id:role/customRoleName");
    // This should trigger a call to Cognito service to get the credentials.
    credentialsProvider.getCredentials();

iOS

If you are using the iOS SDK you can provide the SAML assertion in your AWSIdentityProviderManager as follows.

    - (AWSTask<NSDictionary<NSString*,NSString*> *> *) logins {
        //this is hardcoded for simplicity, normally you would asynchronously go to your SAML provider 
        //get the assertion and return the logins map using a AWSTaskCompletionSource
        return [AWSTask taskWithResult:@{@"arn:aws:iam::aws account id:saml-provider/name":@"base64 encoded assertion response"}];
    }

    // If SAML assertion contains multiple roles, resolve the role by setting the custom role.
    // Implementing this is optional if there is only one role.
    - (NSString *)customRoleArn {
        return @"arn:aws:iam::accountId:role/customRoleName";
    }
    

 

As you can see, integrating a SAML-based IdP with Amazon Cognito provides you flexibility to choose different roles to associate with your organizations’ users and groups to access AWS resources. It is easy to integrate your SAML IdP with Amazon Cognito. This integration enables you to leverage the identity management and data synchronization functionality provided by Amazon Cognito.

We welcome your feedback on this feature.  For more information, see the SAML Identity Provider topic in the Amazon Cognito Developer Guide. You can reach us by posting to the Amazon Cognito forums.

 

 

Integrating Amazon Cognito User Pools with API Gateway

by Patanjal Vyas | on | | Comments

Introduction

In April, we launched the Beta version of a new Amazon Cognito feature called Amazon Cognito User Pools. The user pools feature makes it easy for developers to add sign-up and sign-in functionality to web and mobile applications. In this blog post we will walk through how to integrate Amazon Cognito User Pools with Amazon API Gateway. It’s a assumed that you have a basic understanding of API Gateway and the API Gateway’s custom authorizer.

Scenarios for integrating Amazon Cognito User Pools with API Gateway

After the successful user authentication in your mobile or web application, your application will need to perform operations in the context of that user. For that you need a back-end application running on your server. In this post, we walk through Amazon Cognito User Pools and API Gateway integration in the context of a Notes application and a service that lets users create, retrieve, and delete notes.

In order to provide a ‘Notes Service’, you first require sign-up and sign-in functionality for your web or mobile application. This functionality can be implemented using Amazon Cognito User Pools. Additionally, you also expose an API for the Notes service though API Gateway. This API creates, retrieves, and deletes notes for an authenticated user. You also must implement authorization in your API so that you can identify the authenticated user and perform operations in the context of that user, such as Create Note and Delete Note.

After a user signs in successfully to your Notes application, Amazon Cognito User Pools returns an ID and Access Token to your app for the authenticated user. The Access Token can then be used to authorize API invocations through API Gateway using the API Gateway’s custom authorizer. For more information on tokens, see Using Tokens with Amazon Cognito User Pools.

Develop a sample Notes Service using AWS Lambda and API Gateway

The following steps describe how to develop the Notes service and its integration with API Gateway and Amazon Cognito User Pools. If you are familiar with API Gateway, you can skim through this section without creating an actual API.

1. Create a ‘Notes’ table that stores notes for your users in Amazon DynamoDB. The primary partition key for the table will be a ‘userId’ string, and the primary sort key will be a ‘noteId’ string. The userId is a globally unique identifier of an authenticated user in your user pool. The noteId is a unique ID associated with each note created by the user.

2. Create a new AWS Lambda function, called ‘dynamodb_manager’, using a Lambda blueprint, ‘simple-mobile-backend’. Choose the Lambda function role with proper permissions. We will use this function in API Gateway to perform operations against the ‘Notes’ table.

3. Create an API named ‘NotesService’ in API Gateway. Create a ‘/notes’ resource with a ‘POST’ method. For Integration Type, choose Lambda function and choose ‘dynamodb_manager’ as the Lambda function.

4. Create a ‘NoteCreateModel’ model in your ‘NotesService’ API and add it to a method request, as follows:

{
  "title": "Note Create Model",
  "type" : "object",
  "properties" : {
    "noteid" : {
       "type" : "string"
    },
    "note" : {
      "type" : "string"
    }
   },
  "required" : ["noteid", "note"]
}

This defines the input for a POST method for a ‘/notes’ resource. In our model, we accept a ‘note’ and ‘noteId’ pair to create a note for a user. The other information we need to create a note for a user is a userid. We will get the userid from the access token which will be passed in an ‘Authorization’ header to your API.

6. Add the following body mapping template to your integration request.

#set($inputRoot = $input.path('$'))
{
  "operation": "create",
  "payload": {
      "Item" : {
          "userid" : "$context.authorizer.principalId",
          "noteid" : "$inputRoot.noteid",
          "note" : "$inputRoot.note"}
  },
  "tableName" : "Notes"
}

This invokes the ‘dynamodb_manager’ Lambda function and creates a note in the ‘Notes’ table. Notice how we retrieve the userid here: ‘$context.authorizer.principalId’ will return the globally unique identifier for an authenticated user.

7. Create a new model ‘Success’ and attach it to your method response, as follows:

{
  "$schema" : "http://json-schema.org/draft-04/schema#",
  "title" : "Success Schema",
  "type" : "object",
  "properties" : {
    "message" : { "type" : "string" }
  }
}

8. Create a body mapping template in your integration response, as follows:

#set($inputRoot = $input.path('$'))
{
  "message" : "Note created"
}

This is the static response you will send to the application each time a note is successfully created. You can create additional body mapping templates to map errors.

Develop a Custom Authorizer for Amazon Cognito User Pools

After your API is created, you need to implement a custom authorizer for your API that will ensure that a request is coming from an authenticated user of your application. If it’s a valid access token, we will generate a policy against a userId, which is a unique identifier (UUID) for a user. You can then use ‘$context.authorizer.principalId’ in your API and get the userId value.

 

1. Download the blueprint for custom authorizer for Amazon Cognito User Pools

2. Unzip the file and modify the following variables with your userPoolId and region in the ‘authorizer.js’ file, as follows:

var userPoolId = ‘{REPLACE_WITH_YOUR_POOL_ID}’;
var region = ‘{REPLACE_WITH_YOUR_REGION}’;

3. Zip all the files again, name the .zip file cup_authorizer.zip, and create a Lambda function with that .zip file. Make sure that you only zip the inner files (authorizer.js and node_modules); do not zip the outer directory.

4. Choose Node.JS 4.3 as the Runtime for the Lambda function. For Handler, choose authorizer.handler.

 
 
 

5. Create a custom authorizer in your API, as shown next.

 

6. Now attach the authorizer in Method Request, as shown next.

 

7. Deploy the API. You can now test the API. If a valid Access Token for your user pool is passed to an API, the API will create a note in a DynamoDB table for that user. Otherwise, the API will throw an unauthorized message back to the application. Here’s how your request would look:

Endpoint: {invokeUrlForYourAPI}/notes

Http Method : POST

Headers

Authorization : {Access Token of a user from your user pool}

Content-Type : application/json

Body

{
  "noteid" : "note1",
  "note" : "my first note"
}

This should create a note in DynamoDB for an authenticated user. If you pass an invalid Access Token or the Access Token is expired, a custom authorizer will throw an unauthorized message (401) back to the client.

Understanding the code

It is important to understand the code in the ‘authorizer.js’ file if you choose to make any further modifications.

1. The following packages must be installed and used in your Lambda function:

var jwt = require('jsonwebtoken');
var request = require('request');
var jwkToPem = require('jwk-to-pem');

2. Download the JSON Web Key Set (JWK Set) for your user pool and convert the keys to PEM format, as follows:

    var pems;

    if (!pems) {
    //Download the JWKs and save it as PEM
    request({
       url: iss + '/.well-known/jwks.json',
       json: true
     }, function (error, response, body) {
        if (!error && response.statusCode === 200) {
            pems = {};
            var keys = body['keys'];
            for(var i = 0; i < keys.length; i++) {
                //Convert each key to PEM
                var key_id = keys[i].kid;
                var modulus = keys[i].n;
                var exponent = keys[i].e;
                var key_type = keys[i].kty;
                var jwk = { kty: key_type, n: modulus, e: exponent};
                var pem = jwkToPem(jwk);
                pems[key_id] = pem;
            }
            //Now continue with validating the token
            ValidateToken(pems, event, context);
        } else {
            //Unable to download JWKs, fail the call
            context.fail("error");
        }
    });
    } else {
        //PEMs are already downloaded, continue with validating the token
        ValidateToken(pems, event, context);
    };
Instead of downloading the JWK Set directly from your Lambda function, you can download it manually once, converting the keys to PEMs and uploading them with your Lambda function.
 

3. Validate the token, as follows:

function ValidateToken(pems, event, context) {

    var token = event.authorizationToken;
    //Fail if the token is not jwt
    var decodedJwt = jwt.decode(token, {complete: true});
    if (!decodedJwt) {
        console.log("Not a valid JWT token");
        context.fail("Unauthorized");
        return;
    }

    //Fail if token is not from your User Pool
    if (decodedJwt.payload.iss != iss) {
        console.log("invalid issuer");
        context.fail("Unauthorized");
        return;
    }

    //Reject the jwt if it's not an 'Access Token'
    if (decodedJwt.payload.token_use != 'access') {
        console.log("Not an access token");
        context.fail("Unauthorized");
        return;
    }

    //Get the kid from the token and retrieve corresponding PEM
    var kid = decodedJwt.header.kid;
    var pem = pems[kid];
    if (!pem) {
        console.log('Invalid access token');
        context.fail("Unauthorized");
        return;
    }

    //Verify the signature of the JWT token to ensure it's really coming from your User Pool

    jwt.verify(token, pem, { issuer: iss }, function(err, payload) {
      if(err) {
        context.fail("Unauthorized");
      } else {
        //Valid token. Generate the API Gateway policy for the user
        //Always generate the policy on value of 'sub' claim and not for 'username' because username is reassignable
        //sub is UUID for a user which is never reassigned to another user.

        var principalId = payload.sub;

        //Get AWS AccountId and API Options
        var apiOptions = {};
        var tmp = event.methodArn.split(':');
        var apiGatewayArnTmp = tmp[5].split('/');
        var awsAccountId = tmp[4];
        apiOptions.region = tmp[3];
        apiOptions.restApiId = apiGatewayArnTmp[0];
        apiOptions.stage = apiGatewayArnTmp[1];
        var method = apiGatewayArnTmp[2];
        var resource = '/'; // root resource
        if (apiGatewayArnTmp[3]) {
            resource += apiGatewayArnTmp[3];
        }

        //For more information on specifics of generating policy, see the blueprint for the API Gateway custom
        //authorizer in the Lambda console

        var policy = new AuthPolicy(principalId, awsAccountId, apiOptions);
        policy.allowAllMethods();

        context.succeed(policy.build());
      }
});
};

When you successfully return the policy from your Lambda function, you can then retrieve the userId value of an authenticated user in your API by using ‘$context.authorizer.principalId’. When you call context.fail("Unauthorized") from your function, it will send a 401 response back to the client. When you call context.fail("error"), it should send a 500 response back to the client.

API Gateway’s Authorizer for Cognito User Pools

API Gateway has recently launched support for Cognito User Pool Authorizer. If you use Cognito User Pool Authorizer, you do not need to set up your own custom authorizer to validate tokens. Once your API methods are configured with Cognito User Pool Authorizer, you can pass unexpired ID Token in the Authorization header to your API methods. If it’s a valid ID Token for a user of your User Pool, you can then access all the claims of ID Token in your API using ‘$context.authorizer.claims’. For example ‘$context.authorizer.claims.email’ will return user’s email address and ‘$context.authorizer.claims.sub’ will return you user’s unique identifier. If the ID token is expired or is invalid, Cognito User Pool Authorizer will send Unauthorized (401) response to the caller. 

 

We welcome your feedback on this feature in the Amazon Cognito forum.

Accessing Your User Pools using the Amazon Cognito Identity SDK for JavaScript

by Ionut Trestian | on | in AWS Mobile | | Comments

Introduction

In April, we launched the beta version of a new Amazon Cognito feature called Your User Pools. Among other functionality, the User Pools feature makes it easy for developers to add sign-up and sign-in functionality to web apps. AWS Mobile SDKs for Android, JavaScript, and iOS are available with this beta launch. In this blog post we will show you how to access the new functionality by using the Amazon Cognito Identity SDK for JavaScript.

Integrating your user pool into your web app

To integrate this new feature into your app, follow the instructions in the Announcing Your User Pools in Amazon Cognito blog post to create your user pool. Note that Generate client secret must be unchecked when creating a web app; the Amazon Cognito Identity SDK for JavaScript doesn’t support apps that have a client secret simply because the client secret could be easily viewed in your code.

Also for brevity, in this blog post we will focus on the functionality provided by the Amazon Cognito Identity SDK for JavaScript. Information about dependencies and setup instructions can be found in the GitHub repository.

Using your user pool in your web app

The first step for interacting with the new feature is to create a CognitoUserPool object by providing a UserPoolId and a ClientId. As mentioned, the SDK does not support the app client secret. If you configure your user pool app client with an app client secret, the SDK will throw exceptions.

Throughout the examples in this post, we will use the userPool object, the userData object (containing the user pool) and the username object, as shown in the following.

AWSCognito.config.region = 'us-east-1';

var poolData = {
    UserPoolId : '...', // your user pool id here
    ClientId : '...' // your client id here
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var userData = {
    Username : '...', // your username here
    Pool : userPool
};

After creating a user pool object, users can be signed up for the application. The necessary information about the user can be collected through the web UI and used to populate CognitoUserAttribute objects that are passed in the signUp call.

var attributeList = [];
    
var dataEmail = {
    Name : 'email',
    Value : '...' // your email here
};
var dataPhoneNumber = {
    Name : 'phone_number',
    Value : '...' // your phone number here with +country code and no delimiters in front
};
var attributeEmail = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataEmail);
var attributePhoneNumber = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(dataPhoneNumber);

attributeList.push(attributeEmail);
attributeList.push(attributePhoneNumber);

var cognitoUser;
userPool.signUp('username', 'password', attributeList, null, function(err, result){
    if (err) {
        alert(err);
        return;
    }
    cognitoUser = result.user;
    console.log('user name is ' + cognitoUser.getUsername());
});

After signing up, the user needs to confirm the sign-up by entering a code sent either through SMS or email (based on the user pool settings). Alternatively, you can use a PreSignUp AWS Lambda function to automatically confirm your users. To confirm sign-up, you must collect the code (‘123456’ in the following example) received by the user and use it as follows:

cognitoUser.confirmRegistration('123456', true, function(err, result) {
    if (err) {
        alert(err);
        return;
    }
    console.log('call result: ' + result);
});

The registration code can be resent by using the resendConfirmationCode method of a cognitoUser object. This is an unauthenticated call and only the username, the client ID, and the user pool information are needed.

A confirmed user can authenticate to obtain a session. The session contains an ID token that contains user claims, an access token that is used internally to perform authenticated calls, and a refresh token that is used internally to refresh the session after it expires each hour. For more information about tokens, see Using Tokens with Amazon Cognito Identity User Pools in the Amazon Cognito Developer Guide. When authentication is successful, the onSuccess callback is called. If authentication fails, the onFailure callback is called. If authentication requires MFA, the mfaRequired callback is called. You must invoke sendMFACode on the cognitoUser object. The verification code that is received must be passed and the user is finally authenticated.

var authenticationData = {
    Username : '...', // your username here
    Password : '...', // your password here
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);

var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: function (result) {
        console.log('access token + ' + result.getAccessToken().getJwtToken());
    },

    onFailure: function(err) {
        alert(err);
    },
    mfaRequired: function(codeDeliveryDetails) {
        var verificationCode = prompt('Please input verification code' ,'');
        cognitoUser.sendMFACode(verificationCode, this);
    }
});

After authenticating, a user can perform authorized operations such as, retrieve user attributes, verify user attributes (such as an unverified email address), delete user attributes, update user attributes, change the user password, and delete the user’s account. For user pools that have an MFA setting of optional, users can enable or disable MFA just for themselves, at a user level. Signing out from the application clears the local user session so the user must authenticate again to establish a new session.

If users forget their passwords, they can initiate a forgotten password flow. A code will be sent to the user. The user uses this code together with a new password to complete the flow.  The relevant call is forgotPassword on a cognitoUser object that is unauthenticated; the relevant callbacks can be seen in the following.

cognitoUser.forgotPassword({
    onSuccess: function (result) {
        console.log('call result: ' + result);
    },
    onFailure: function(err) {
        alert(err);
    },
    inputVerificationCode() {
        var verificationCode = prompt('Please input verification code ' ,'');
        var newPassword = prompt('Enter new password ' ,'');
        cognitoUser.confirmPassword(verificationCode, newPassword, this);
    }
});

If you want to work with other AWS services, you must first create a federated identity pool. After you create this identity pool, you can get AWS credentials by passing the identity pool ID and the ID token (obtained earlier) when authenticating. The following example shows how to populate IdentityPoolId and pass the ID token through the Logins map.

AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    IdentityPoolId: 'us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
    Logins: {
        'cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX': result.getIdToken().getJwtToken()
    }
});

AWS.config.credentials.get(function(err){
    if (err) {
        alert(err);
    }
});

More examples and an overview of the code can be found in the GitHub repository. We welcome your feedback on this feature. You can reach us by creating an issue in the GitHub repository or posting to the Amazon Cognito forums.

Using Android SDK with Amazon Cognito Your User Pools

by Mahesh Reddy | on | | Comments

Introduction

Last month AWS launched the beta version of  Amazon Cognito User Pools. With this feature, you can easily add sign-up and sign-in functionality to your mobile and web applications. You get a simple to use, fully managed service for creating and maintaining a user directory which can scale to hundreds of millions of users. You also benefit from the security and privacy best practices of AWS.

AWS Mobile SDKs for Android, JavaScript, and iOS are available with this beta launch. You can use these SDKs in your apps to interact with your user pools. In this blog we will show how to get started with the Mobile SDK for Android. For guidance on iOS or JavaScript see the Amazon Cognito Developer Guide. For a walk-through on getting started with iOS, please visit this blog.

 

Creating your user pools

Before you can use the SDK, you must create a user pool.  

1) Start by signing in to the Amazon Cognito console and choosing Manage your User Pools. This opens the page where you can see all of your Cognito user pools and also create new pools.

2) Provide a name for your pool and choose Step through settings to start customizing the pool. You can choose Review defaults to create the pool with default settings.

3) Choose the required attributes for users in your pool. The attributes you select as "Required" are must-have values for users to sign-up in the pool. Some attributes can be used as aliases, which means that users can sign in with the aliases instead of their username, if these attributes are verified. Let’s choose email, given name and phone number as required parameters and email and phone number as aliases. If a user in this pool has verified his or her email address, then this user can use that email address to sign in. The user will still be able to sign in with his or her username.

4) The next step is to set the password requirements; we will use default password settings.

5) In the next step you can enable multi-factor authentication (MFA) for users in this pool. We will leave MFA turned off for users in this pool.

6) Next, you can customize the messages that are sent to users in your pool to deliver various verification codes.

7) The next step is to create a client ID for your app. A client ID is required to allow your app to interact with your user pool. Click Add an app and provide your the name of your app. This can be any string that helps you identify how this client-id is used, e.g., "my social network Android app".

You can also generate a client secret along with the client ID, but it’s not required. If you chose to generate a client secret for your client ID you must use the generated client secret whenever you use the client ID in your apps.

8) The next step allows you to customize workflows through Lambda triggers. For this walkthrough we will not set up customization through Lambda.

9) Review the settings and choose “Create pool” to create your user pool.

You now have a user pool and users can sign up in this pool.

 

Using Amazon Cognito User Pools in your Android app

Start by creating a CognitoUserPool object. This will be the entry point for all interactions with your Cognito user pool from your app. To create this object, you will need the poolId, clientId and clientSecret (if generated for the clientId).

// setup AWS service configuration. Choosing default configuration
ClientConfiguration clientConfiguration = new ClientConfiguration();	

// Create a CognitoUserPool object to refer to your user pool
CognitoUserPool userPool = new CognitoUserPool(context, poolId, clientId, clientSecret, clientConfiguration);

Using this pool instance, you can sign up new users to the pool. To sign up a user, you will need to provide the following information from the user:

1.     user-id: This will be used by the user to login and must be unique with-in the pool.

2.     password: This will be the users’ password.

3.     User attributes: These are other user details. They must include the required attributes set for your pool. We had earlier set email, given name, and phone number as required attributes.

After you collect these details from your user, you can use the pool instance to sign up the user.

// Create a CognitoUserAttributes object and add user attributes 
CognitoUserAttributes userAttributes = new CognitoUserAttributes(); 

// Add the user attributes. Attributes are added as key-value pairs 
// Adding user's given name. 
// Note that the key is "given_name" which is the OIDC claim for given name 
userAttributes.addAttribute("given_name", userGivenName); 

// Adding user's phone number 
userAttributes.addAttribute("phone_number", phoneNumber); 

// Adding user's email address 
userAttributes.addAttribute("email", emailAddress);

Create a callback handler for sign up. The onSuccess method is called when the sign-up is successful. 

SignUpHandler signupCallback = new SignUpHandler() {

    @Override
    public void onSuccess(CognitoUser cognitoUser, boolean userConfirmed, CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) {
        // Sign-up was successful
        
        // Check if this user (cognitoUser) has to be confirmed
        if(!userConfirmed) {
            // This user has to be confirmed and a confirmation code was sent to the user
	    // cognitoUserCodeDeliveryDetails will indicate where the confirmation code was sent
	    // Get the confirmation code from user
        }
	else {
            // The user has already been confirmed
        }
    }
 
    @Override
    public void onFailure(Exception exception) {
        // Sign-up failed, check exception for the cause
    }
};

Call sign-up API 

// Sign up this user
userPool.signUpInBackground(userId, password, userAttributes, null, signupCallback);

Users may have to be confirmed after they sign up, before they can sign in. Users can confirm through email or phone number. After a successful sign-up, if the user has to be confirmed, a confirmation code will be sent to the user’s email or phone number. It is also possible to automatically confirm a user after sign-up through Lambda triggers.

If a user provides an email address or phone number during sign-up, and you have selected automatic verification for your user pool, a confirmation code will be sent to the user’s phone number as a text message or to the user’s email address. The cognitoUserCodeDeliveryDetails object, which was delivered to the callback handler on successful sign-up, will indicate where this confirmation code was sent. You can use this to let the user known where to expect the confirmation code.

Create a callback handler to confirm the user. This callback handler is used by the SDK to  communicate the results of the confirmation API call.

// Call back handler for confirmSignUp API 
GenericHandler confirmationCallback = new GenericHandler() { 
    
    @Override 
    public void onSuccess() { 
        // User was successfully confirmed
    } 

    @Override
    public void onFailure(Exception exception) { 
        // User confirmation failed. Check exception for the cause.
    } 
};

When a new user is confirmed, the user’s attribute through which the confirmation code was sent (email or phone number) will be marked as verified. If this attribute is also set to be used as an alias, then the user will be able to sign-in with that attribute (email or phone number) instead of the username.

Alias values must be unique in a pool, and you have choices to resolve the situation if the same email or phone number is already being used as an alias for an existing user in the pool.

You can allow the user confirmation to fail if such conflicts are found, you can do this by setting the forcedAliasCreation parameter to false. The attribute will then remain verified for the existing user, and will continue to be an alias for the existing user. The new user will remain un-confirmed. 

Setting this parameter to true will resolve the conflict by marking the attribute (iemail or phone number) as verified for the new user, and consequently marking it as not-verified for the existing user. This attribute is no longer an alias for the existing user.

// This will cause confirmation to fail if the user attribute has been verified for another user in the same pool  
boolean forcedAliasCreation = false; 

// Call API to confirm this user 
cognitoUser.confirmSignUpInBackground(confirmationCode, forcedAliasCreation, confirmationCallback);

All confirmed users can sign-in. On successful sign-in, access and iID tokens are returned. These tokens are encapsulated in a CognitoUserSession object.

To sign in, start by creating an callback handler for authentication. During the sign-in process the SDK will interact with your app through this callback handler.

// Callback handler for the sign-in process 
AuthenticationHandler authenticationHandler = new AuthenticationHandler() { 
    
    @Override 
    public void onSuccess(CognitoUserSession cognitoUserSession) { 
        // Sign-in was successful, cognitoUserSession will contain tokens for the user   
    }
    
    @Override
    public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) { 
        // The API needs user sign-in credentials to continue
        AuthenticationDetails authenticationDetails = new AuthenticationDetails(userId, password, null);

        // Pass the user sign-in credentials to the continuation
        authenticationContinuation.setAuthenticationDetails(authenticationDetails);

        // Allow the sign-in to continue
        authenticationContinuation.continueTask();
    }

    @Override
    public void getMFACode(MultiFactorAuthenticationContinuation multiFactorAuthenticationContinuation) { 
        // Multi-factor authentication is required, get the verification code from user
        multiFactorAuthenticationContinuation.setMfaCode(mfaVerificationCode);
        // Allow the sign-in process to continue
        multiFactorAuthenticationContinuation.continueTask();
    }

    @Override
    public void onFailure(Exception exception) {
        // Sign-in failed, check exception for the cause
    } 
};

// Sign-in the user 
cognitoUser.getSessionInBackground(authenticationHandler);

After authenticating a user, you can perform other operations on this user, such as getting user details

// Implement callback handler for get details call
GetDetailsHandler getDetailsHandler = new GetDetailsHandler() {
    @Override
    public void onSuccess(CognitoUserDetails cognitoUserDetails) {
        // The user detail are in cognitoUserDetails
    }
        
    @Override
    public void onFailure(Exception exception) { 
        // Fetch user details failed, check exception for the cause
    }
};

// Fetch the user details 
cognitoUser.getDetailsInBackground(getDetailsHandler);

To obtain AWS credentials to access AWS resources for your user, first associate your user pool with an identity pool using the AWS Management Console.  If you haven’t already created an identity pool, do that using the Amazon Cognito Console.  In the Authentication providers choose the Cognito tab, and then specify your User Pool ID and App Client ID.

Now add the ID tokens, received after successful authentication, to your credentials provider.

// Get id token from CognitoUserSession.
String idToken = cognitoUserSession.getIdToken().getJWTToken(); 

// Create a credentials provider, or use the existing provider.
CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(context, IDENTITY_POOL_ID, REGION);

// Set up as a credentials provider.
Map<String, String> logins = new HashMap<String, String>(); 
logins.put("cognito-idp.us-east-1.amazonaws.com/us-east-1_123456678", cognitoUserSession.getIdToken().getJWTToken()); 
credentialsProvider.setLogins(logins);

For further details on integrating Cognito User Pools with Cognito Federated Identity pools see AWS Cognito User Pools documentation.

This was a quick walk-through to get you started with using Amazon Cognito User Pools in your app. You can find further details about the AWS Mobile SDK for Android in the API reference. The sample app is in this GitHub repository.

We would like to hear your comments about this feature. You can reach out to us by posting on the Amazon Cognito forum or the GitHub repository.