AWS Startups Blog

Amazon Cognito & Mobile Apps – Part1: User Identity and Simple AWS Authentication

 By Stefano Buliani, Sr Product Manager, AWS


Amazon Cognito is a user-state synchronization service that helps you create unique identifiers for your end users that are kept consistent across devices and platforms. Cognito also delivers temporary, limited-privilege credentials to your application to access AWS resources.

Data synchronization and AWS authentication are part of that undifferentiated heavy-lifting set of tasks all applications have to perform that consume a significant portion of your time. You should be focused on delivering the best possible experience to your end-users. Cognito takes care of all of this work for you, allowing you to deliver great applications, faster.

Cognito1

By using Amazon Cognito in your web applications as well as mobile apps, you can utilize a consistent, cross-platform identifier for your end users authenticated through Facebook, Google, or Amazon. Together with the Cognito Sync service, this allows you to keep user-related data consistent across all your applications and platforms. Further, Cognito helps you to retrieve temporary, limited-privilege credentials for both your authenticated and unauthenticated users without managing any backend infrastructure. In a previous post, we covered how to connect to the Amazon Cognito Identity service from your mobile applications.

Getting Started

The first step is to create a new identity pool through the Amazon Cognito console. During the creation of the identity pool, you will be asked to create a new IAM role or pick an existing one for your end users. The role you select has an impact on which AWS services your end users will be able to access with the temporary credentials. A new policy created by the Amazon Cognito console by default allows access to the Amazon Cognito sync service and Mobile Analytics.

The Authentication Flow

AWS authentication with Amazon Cognito involves three steps:

  1. Call the GetId API providing your AWS account and identity pool details to retrieve a unique identifier for your end user.
  2. Call the GetOpenIdToken API to receive a valid OpenId Connect token for the unique identifier you retrieve with the GetId call.
  3. Using the STS client make an AssumeRoleWithWebIdentity call to retrieve a set of temporary, limited-privilege credentials you can use to access your AWS resources.

See the Amazon Cognito Identity API reference to learn more about these and other API endpoints exposed by the Cognito service.

The JavaScript SDK abstracts this complexity by exposing a CognitoIdentityCredentials object that takes care of making all of the required API calls, caching the unique identifier for the end user received from the GetId call and renewing the credentials from the AssumeRoleWithWebIdentity call when they expire.

To utilize the Java, .NET, or PHP SDK, you have to implement the authentication flow yourself by making the three API calls. This post guides you through that process.

Using JavaScript

The JavaScript SDK for the Browser includes a CognitoIdentityCredentials object, which abstracts the entire authentication flow. This means you don’t have to call all of the APIs individually but simply utilize the CognitoIdentityCredentials object, pass your identity pool and account parameters, and the credentials object takes care of retrieving AWS credentials and the unique identifier for your end users.

JavaScript

// The parameters required to intialize the Cognito Credentials object.
// If you are authenticating your users through one of the supported
// identity providers you should set the Logins object with the provider
// tokens. For example:
// Logins: {
//   graph.facebook.com : facebookResponse.authResponse.accessToken
// }
var params = {
    AccountId: “YOUR_AWS_ACCOUNT_ID”,
    RoleArn: “arn:aws:iam::6157xxxxxxxx:role/a_valid_aws_role_arn”,
    IdentityPoolId: “YOUR_COGNITO_IDENTITY_POOL_ID”
};
// set the Amazon Cognito region
AWS.config.region = ‘us-east-1';
// initialize the Credentials object with our parameters
AWS.config.credentials = new AWS.CognitoIdentityCredentials(params);
// We can set the get method of the Credentials object to retrieve
// the unique identifier for the end user (identityId) once the provider
// has refreshed itself
AWS.config.credentials.get(function(err) {
    if (!err) {
        console.log(“Cognito Identity Id: “ + AWS.config.credentials.identityId);
    }
});
// Other service clients will automatically use the Cognito Credentials provider
// configured in the JavaScript SDK.
var cognitoSyncClient = new AWS.CognitoSync();
cognitoSyncClient.listDatasets({
    IdentityId: AWS.config.credentials.identityId,
    IdentityPoolId: “YOUR_COGNITO_IDENTITY_POOL_ID”
}, function(err, data) {
    if ( !err ) {
        console.log(JSON.stringify(data));
    }
});

GetID

The first call you should make is to retrieve a unique identifier for your user. You only need to make this API call the first time a user connects to your app. You should then cache the identity id returned. If a user with a known identity id connects again, you can skip this call. This call can receive an optional parameter containing the tokens returned by the supported identity providers.

Java

// import the required packages from the AWS SDK for Java
import com.amazonaws.services.cognitoidentity.*;
import com.amazonaws.services.cognitoidentity.model.*;
import com.amazonaws.services.securitytoken.*;
import com.amazonaws.services.securitytoken.model.*;
import com.amazonaws.auth.*;

// initialize the Cognito identity client with a set
// of anonymous AWS credentials
AmazonCognitoIdentity identityClient = new AmazonCognitoIdentityClient(new AnonymousAWSCredentials());

// send a get id request. This only needs to be executed the first time
// and the result should be cached.
GetIdRequest idRequest = new GetIdRequest();
idRequest.setAccountId(“YOUR_AWS_ACCOUNT_ID”);
idRequest.setIdentityPoolId(“YOUR_COGNITO_IDENTITY_POOL_ID”);
// If you are authenticating your users through an identity provider
// then you can set the Map of tokens in the request
// Map providerTokens = new HashMap();
// providerTokens.put(“graph.facebook.com”, “facebook session key”);
// idRequest.setLogins(providerTokens);

GetIdResult idResp = identityClient.getId(idRequest);

String identityId = idResp.getIdentityId();

// TODO: At this point you should save this identifier so you won’t
// have to make this call the next time a user connects

.NET

// import the libraries we need from the AWS SDK for .NET
using Amazon;
using Amazon.Runtime;
using Amazon.CognitoIdentity;
using Amazon.CognitoIdentity.Model;
using Amazon.SecurityToken;
using Amazon.SecurityToken.Model;

// initialize a set of anonymous AWS credentials for our API calls
AnonymousAWSCredentials cred = new AnonymousAWSCredentials ();

// initialize the Cognito identity client and prepare a request object
// to get the identity id
AmazonCognitoIdentityClient cognitoClient = new AmazonCognitoIdentityClient(
cred, // the anonymous credentials
RegionEndpoint.USEast1 // the Amazon Cognito region
);

GetIdRequest idRequest = new GetIdRequest ();
idRequest.AccountId = “YOUR_AWS_ACCOUNT_ID”;
idRequest.IdentityPoolId = “YOUR_COGNITO_IDENTITY_POOL_ID”;
// set the Dictionary of logins if you are authenticating users
// through an identity provider
//idRequest.Logins = new Dictionary {
// { “graph.facebook.com”, “FacebookSessionToken” }
//};

// The identity id is in the IdentityId parameter of the response object
GetIdResponse idResp = cognitoClient.GetId (idRequest);

// TODO: At this point you should save this identifier so you won’t
// have to make this call the next time a user connects

PHP

// include the AWS client library in your code
require “aws.phar”;
use AwsCognitoIdentityCognitoIdentityClient;
use AwsStsStsClient;

// initialize a Cognito identity client using the factory
$identityClient = CognitoIdentityClient::factory(array(
‘region’ => ‘us-east-1′
));

// call the GetId API with the required parameters
$idResp = $identityClient->getId(array(
‘AccountId’ => ‘YOUR_AWS_ACCOUNT_ID’,
‘IdentityPoolId’ => ‘YOUR_COGNITO_IDENTITY_POOL_ID’,
// If you are authenticating your users through an identity
// provider then you can set the associative array of tokens
// in this call
// ‘Logins’ => array(
// ‘graph.facebook.com’ => ‘your facebook session token’,
//)
));

// retrieve the identity id from the response data structure
$identityId = $idResp[“IdentityId”];

// TODO: At this point you should save this identifier so you won’t
// have to make this call the next time a user connects

GetOpenIdToken

Once you have retrieved the unique identifier for your end user, or loaded the previous identifier from your cache, you can make the GetOpenIdToken call to retrieve a valid OpenID Connect token for them. The token returned by this call can be traded to the Security Token Service (STS) in exchange for temporary, limited-privilege AWS credentials.

If your identity id has already been authenticated with any supported provider — such as Facebook, Google, or Amazon — you will need to include at least one token for a login linked to this identity in the Logins map.

Java

// Create the request object
GetOpenIdTokenRequest tokenRequest = new GetOpenIdTokenRequest();
tokenRequest.setIdentityId(identityId);
// If you are authenticating your users through an identity provider
// then you can set the Map of tokens in the request
// Map providerTokens = new HashMap();
// providerTokens.put(“graph.facebook.com”, “facebook session key”);
// tokenRequest.setLogins(providerTokens);

GetOpenIdTokenResult tokenResp = identityClient.getOpenIdToken(tokenRequest);
// get the OpenID token from the response
String openIdToken = tokenResp.getToken();

.NET

// create a new request object
GetOpenIdTokenRequest openIdReq = new GetOpenIdTokenRequest ();
openIdReq.IdentityId = idResp.IdentityId;
// set the Dictionary of logins if you are authenticating users 
// through an identity provider
//openIdReq.Logins = new Dictionary {
// { “graph.facebook.com”, “FacebookSessionToken” }
//};

GetOpenIdTokenResponse openIdResp = cognitoClient.GetOpenIdToken(openIdReq);

// the authentication token is available in the Token property
// of the response object

PHP

// execute the getOpenIdToken call with the identityId
// retrieved from the previous call or cached
$tokenResp = $identityClient->getOpenIdToken(array(
    ‘IdentityId’ => $identityId,
    // If you are authenticating your users through an identity
    // provider then you can set the associative array of tokens
    // in this call
    // ‘Logins’ => array(
    // ‘graph.facebook.com’ => ‘your facebook session token’,
    //)
));

// read the OpenID token from the response
$token = $tokenResp[“Token”];

AssumeRoleWithWebIdentity

This call to the Security Token Service retrieves a set of temporary, limited-privilege credentials for your application to access AWS Resources. The permissions associated to these credentials are set by the IAM access policy set on the role. You can additionally specify a “scope-down” policy on this API call to further restrict access. If your application is allowing access to unauthenticated users, i.e., users that did not log in with your application through an identity provider, we recommend you restrict access as much as possible.

Java

// you can now create a set of temporary, limited-privilege credentials to access
// your AWS resources through the Security Token Service utilizing the OpenID
// token returned by the previous API call. The IAM Role ARN passed to this call
// will be applied to the temporary credentials returned
AWSSecurityTokenService stsClient = new AWSSecurityTokenServiceClient(new AnonymousAWSCredentials());
AssumeRoleWithWebIdentityRequest stsReq = new AssumeRoleWithWebIdentityRequest();
stsReq.setRoleArn(“arn:aws:iam::6157xxxxxxxx:role/a_valid_aws_role_arn”);
stsReq.setWebIdentityToken(awsAccessToken);
stsReq.setRoleSessionName(“AppTestSession”);

AssumeRoleWithWebIdentityResult stsResp = stsClient.assumeRoleWithWebIdentity(stsReq);
Credentials stsCredentials = stsResp.getCredentials();

// Create the session credentials object
AWSSessionCredentials sessionCredentials = new BasicSessionCredentials(
stsCredentials.getAccessKeyId(),
stsCredentials.getSecretAccessKey(),
stsCredentials.getSessionToken()
);
// save the timeout for these credentials
Date sessionCredentialsExpiration = stsCredentials.getExpiration();

// these credentials can then be used to initialize other AWS clients,
// for example the Amazon Cognito Sync client
AmazonCognitoSync syncClient = new AmazonCognitoSyncClient(sessionCredentials);
ListDatasetsRequest syncRequest = new ListDatasetsRequest();
syncRequest.setIdentityId(idResp.getIdentityId());
syncRequest.setIdentityPoolId(“YOUR_COGNITO_IDENTITY_POOL_ID”);
ListDatasetsResult syncResp = syncClient.listDatasets(syncRequest);

.NET

// create a new security token service client with the 
// anonymous credentials we initialized in the first step
AmazonSecurityTokenServiceClient stsClient = new AmazonSecurityTokenServiceClient(
 cred, 
 RegionEndpoint.USEast1
);

// create a new AssumeRoleWithWebIdentityRequest object and set
// the parameters we need
AssumeRoleWithWebIdentityRequest stsReq = new AssumeRoleWithWebIdentityRequest();
stsReq.RoleArn = “arn:aws:iam::6157xxxxxxxx:role/a_valid_aws_role_arn”;
stsReq.RoleSessionName = “AppTestSession”; // you need to give the session a name
stsReq.WebIdentityToken = openIdResp.Token; // the Token from the previous API call

// execute the request and retrieve the credentials
AssumeRoleWithWebIdentityResponse stsResp = stsClient.AssumeRoleWithWebIdentity(stsReq);

// you can now initialize other AWS clients with these credentials,
// for example the Amazon Cognito Sync client
AmazonCognitoSyncClient cognitoSyncClient = new AmazonCognitoSyncClient (stsResp.Credentials);
ListDatasetsRequest syncRequest = new ListDatasetsRequest ();
syncRequest.IdentityId = idResp.IdentityId;
syncRequest.IdentityPoolId = “us-east-1:6df80629–0e7e-432f-a730–23898c0e3901″;
ListDatasetsResult syncResp = cognitoSyncClient.ListDatasets(syncRequest);

PHP

// create a new STS client
$stsClient = StsClient::factory(array(
    ‘region’ => ‘us-east-1'
));

// run the AssumeRoleWithWebIdentity request with the IAM role
// for your user and the OpenID token retrieved from the previous
// API call
$stsResp = $stsClient->assumeRoleWithWebIdentity(array(
‘RoleArn’ => ‘arn:aws:iam::6157xxxxxxxx:role/a_valid_aws_role_arn’,
‘RoleSessionName’ => ‘AppTestSession’, // you need to give the session a name
‘WebIdentityToken’ => $token
));

// you can use the credentials from the STS call response to
// initialize other AWS clients. For example the Amazon Cognito Sync client
$cognitSync = CognitoSyncClient::factory(array(
‘key’ => $stsResp[‘Credentials’][‘AccessKeyId’],
‘secret’ => $stsResp[‘Credentials’][‘SecretAccessKey’],
‘token’ => $stsResp[‘Credentials’][‘SessionToken’],
‘region’ => ‘us-east-1′
));
$syncResp = $cognitoSync->listDatasets(array(
‘IdentityId’ => $identityId,
‘IdentityPoolId’ => ‘YOUR_COGNITO_IDENTITY_POOL_ID’
));

Conclusion

Amazon Cognito makes it simple to securely access your AWS resources from your web applications as well as mobile apps by delivering temporary, limited-privilege credentials. Further, you can keep a consistent end-user identifier in both your web and mobile applications without setting up any infrastructure and managing a backend service. You can also utilize the Amazon Cognito Sync service to save profile data for a user and make it easily accessible from all the platforms supported by your application. The next post will explain how to make the most of the fine-grained access permissions in IAM policies utilizing the Cognito identifier.