Front-End Web & Mobile
Tracking and Remembering Devices Using Amazon Cognito Your User Pools
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.