Web Identity Federation with Mobile Applications

Discussing the web identity federation feature of AWS Security Token Service and a sample for use in the AWS Mobile SDKs.


Submitted By: BobK@AWS
Created On: October 19, 2017


Introducing Web Identity Federation

AWS Security Token Service (STS) now offers Web Identity Federation (WIF). This allows a developer to federate their application from Facebook, Google, or Amazon with their AWS account, allowing their end users to authenticate with one of these Identity Providers (IdP) and receive temporary AWS credentials. In combination with Policy Variables, WIF allows the developer to restrict end users' access to a subset of AWS resources within their account.

To help you understand how web identity federation works, you can use the Web Identity Federation Playground. This interactive website lets you walk through the process of authenticating via Login with Amazon, Facebook, or Google, getting temporary security credentials, and then using those credentials to make a request to AWS.

This article shows how WIF can be used to give many users a "Personal File Store" all housed within a single Amazon S3 bucket without the need for any backend infrastructure. It is adapted from a previous article which used a custom Token Vending Machine hosted in AWS Elastic Beanstalk.

Policies

You use policies to specify permissions for an IAM user, an IAM role, or for temporary credentials. For web identity federation, there are two policies: one that specifies who can assume the role (the trusted entity, or principal), and another that specifies the AWS actions and resources that the user who assumes the role has access to (the access policy). When creating your role for web identity federation via the AWS Management Console, the Role Creation wizard walks you through the process of creating the trusted entity policy, but you will need to create the access policy by hand. Here's the access policy you should use with the sample for Facebook login.

 

{
 "Version":"2012-10-17",
 "Statement":[{
   "Effect":"Allow",
   "Action":["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
   "Resource":[
       "arn:aws:s3:::__BUCKET_NAME__/${graph.facebook.com:id}",
       "arn:aws:s3:::__BUCKET_NAME__/${graph.facebook.com:id}/*"
   ]
  },
  {
   "Effect":"Allow",
   "Action":["s3:ListBucket"],
   "Resource":["arn:aws:s3:::__BUCKET_NAME__"],
   "Condition": 
     {"StringLike": 
       {"s3:prefix":"${graph.facebook.com:id}/*"}
     }
  }
 ]
}

You should replace the string __BUCKET_NAME__ with the name for an appropriate bucket in your AWS account. In the preceding policy, each statement has a specific effect:

  • The first statement enables each application user to get, put, and delete objects in the specified Amazon S3 bucket, but requires that the object name be prefixed with the users's Facebook user ID. This has the effect of "partitioning" the users into separate "folders" of the Amazon S3 bucket.
  • The second statement enables users to list only their objects' contents by enforcing the same prefix condition on the specified bucket.

Amazon S3 Personal File Store Sample Application

Connecting with the Identity Provider

web identity federation and this sample application support three identity providers: Amazon, Facebook, and Google. By default, only Facebook login is enabled in the sample and this article discusses how to integrate Facebook. Please refer to the README HTML files included with the sample to see how to enable login with Google and Amazon. This sample includes version 3.2.1 of the Facebook SDK for iOS and version 3.0.1 of the Facebook SDK for Android. Using newer versions of the Facebook SDK may require modifications to the sample for compatibility.

After successfully authenticating with Facebook, your application is able to access a Facebook Session (FBSession on iOS). This session includes an access_token which your application needs to send to AWS STS along with some additional information.

  • iOS

 

SecurityTokenServiceAssumeRoleWithWebIdentityRequest *request =
      [[[SecurityTokenServiceAssumeRoleWithWebIdentityRequest alloc] init] autorelease];
request.webIdentityToken = session.accesToken;
request.providerId = @"graph.facebook.com";
request.roleArn = ROLE_ARN;
request.roleSessionName = @"wifSession";

SecurityTokenServiceAssumeRoleWithWebIdentityResponse *response = [sts assumeRoleWithWebIdentity:request];

NSString *subjectFromWIF = response.subjectFromWebIdentityToken;
AmazonCredentials *credentials = [[[AmazonCredentials alloc] initWithAccessKey:response.credentials.accessKeyId
                                                                 withSecretKey:response.credentials.secretAccessKey
                                                             withSecurityToken:response.credentials.sessionToken] autorelease];

s3 = [[AmazonS3Client alloc] autorelease];
[s3 initWithCredentials:credentials];
  • Android

 

AssumeRoleWithWebIdentityRequest request = new AssumeRoleWithWebIdentityRequest()
                                                .withWebIdentityToken(session.getAccessToken())
                                                .withProviderId("graph.facebook.com")
                                                .withRoleArn(ROLE_ARN)
                                                .withRoleSessionName("wifSession");

AssumeRoleWithWebIdentityResult result = sts.assumeRoleWithWebIdentity(request);

Credentials stsCredentials = result.getCredentials();
String subjectFromWIF = result.getSubjectFromWebIdentityToken();
BasicSessionCredentials credentials = new BasicSessionCredentials(stsCredentials.getAccessKeyId(),
                                                                  stsCredentials.getSecretAccessKey(),
                                                                  stsCredentials.getSessionToken());

s3Client = new AmazonS3Client(credentials);

In the request, the application needs to supply some required additional information:

  • ProviderId — the name of the identity provider or IdP. The supported values are graph.facebook.com or www.amazon.com. For Google this value is left unassigned.
  • RoleArn — the Amazon Resource Name (ARN) of the Role the user will assume. The ARN is of the form arn:aws:iam::123456789:role/myWebIdentityRole.
  • RoleSessionName — the name to give to this session. The name is used to identify the session.

It is also possible to control the duration of the session token generated, although the same upper limits hold for WIF calls as for other AWS STS calls. Refer to the AWS Security Token Service API Reference for more details.

If you call AWS STS as above, you still need to extract the identity provider's credentials and user identifier from the response object and pass them to your individual service clients. The application also needs to call AssumeRoleWithWebIndentity each time the AWS credentials expire. To help with managing WIF credentials, both the iOS and Android SDKs offer a credentials provider that wraps the calls to AssumeRoleWithWebIdentity.

  • iOS

 

AmazonWIFCredentialsProvider *wif = [[AmazonWIFCredentialsProvider alloc] initWithRole:ROLE_ARN
                                                                   andWebIdentityToken:self.session.accessTokenData.accessToken
                                                                          fromProvider:@"graph.facebook.com"];

NSString *subjectFromWIF = wif.subjectFromWIF;
s3 = [[[AmazonS3Client alloc] autorelease] initWithCredentialsProvider:wif];
  • Android

 

WebIdentityFederationSessionCredentialsProvider wif =
         new WebIdentityFederationSessionCredentialsProvider(fbSession.getAccessToken(),
                                                             "graph.facebook.com",
                                                             ROLE_ARN);
String subjectFromWIF = wif.getSubjectFromWIF();
s3 = new AmazonS3Client(wif);

On iOS, the credentials provider handles refreshing the AWS credentials for you; on Android, refresh() may need to be called manually. Unfortunately, the identity provider's token (access_token for Facebook) also has an expiration. On both iOS and Android, the provider does not manage the expiration of the identity provider's credentials. Please refer to the individual provider's documentation for how to manage expiration of credentials.

Accessing Partitioned Amazon S3

The policy we applied to our role for web identity federation requires the AWS Mobile SDKs to be used in a specific fashion in order to list, get, and put objects within the user's partition of Amazon S3. In the following section, we show the relevant code snippets from the sample application.

Put Object

Putting objects into the application bucket requires that the user's username be a prefix followed by the forward slash ("/") character.

  • iOS

 

NSString *keyName = [NSString stringWithFormat:@"%@/%@", subjectFromWIF, objectName.text];
        
S3PutObjectRequest *por = [[[S3PutObjectRequest alloc] initWithKey:keyName inBucket:__BUCKET_NAME__] autorelease];
por.data = data;

[s3Client putObject:por];
  • Android

 

ByteArrayInputStream bais = new ByteArrayInputStream( data.getBytes() );
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength( data.getBytes().length );

String keyName = subjectFromWIF + "/" + objectName.getText().toString();
s3Client.putObject(__BUCKET_NAME__, keyName, bais, metadata );

Listing Objects

The only requirement to list objects in the application's bucket is to provide the prefix. The prefix is the user's username followed by a forward slash ("/"), as required by the policy.

  • iOS

 

S3ListObjectsRequest  *listObjectsRequest = [[[S3ListObjectsRequest alloc] initWithName:__BUCKET_NAME__] autorelease];
listObjectRequest.prefix = [NSString stringWithFormat:@"%@/", subjectFromWIF];

S3ListObjectsResponse *listObjectResponse = [s3Client listObjects:listObjectRequest];
  • Android

 

ListObjectsRequest req = new ListObjectsRequest();
req.setBucketName( __BUCKET_NAME__ );
req.setPrefix( subjectFromWIF + "/" );
		
ObjectListing objects = s3Client.listObjects( req );

Get Object

When listing the objects, the names returned contain the user-specific prefix as part of the name. Therefore, getting objects does not require additional logic.

  • iOS

 

S3GetObjectRequest  *getObjectRequest  = [[[S3GetObjectRequest alloc] initWithKey:__OBJECT_NAME__ 
                                                                       withBucket:__BUCKET_NAME__] autorelease];
S3GetObjectResponse *getObjectResponse = [s3Client getObject:getObjectRequest];
  • Android

 

S3Object objectData = s3Client.getObject( __BUCKET_NAME__, __OBJECT_NAME__ );                   

References

The full sample code is included in the AWS Mobile SDKs. Follow these links to download the AWS Mobile SDKs:

For more information about policies, see the following topics:

For more information about the AWS Security Token Service, see the following topic:

 

Want to learn more?

To learn about mobile development best practices, follow our AWS Mobile Development Blog. You can also ask questions or post comments in the Mobile Development Forum about this or any other topic related to mobile development with AWS.