[Amazon Cognito] New feature: Original Identity Systems Supported

2014.10.01

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

This post is English translation of “[Amazon Cognito] Facebook / Google / Amazon だけじゃない!独自の認証システムも利用可能になりました!” written by Yuki Suwa, an iOS and Android Developer at underscore, Inc.

Custom Identity Provider become available

Yesterday(9/30)、Developer Authenticated Identities became available for all mobile App Developers. This feature is provided by Amazon Cognito.

As an Identity Provider, AWS had supported Amazon, Facebook, and Google accounts so far. However, this update enables to use any Service Provider. If you have any authentication system besides, the authenticated users can use unique Identity ID and temporary AWS credentials.

Background

So far, custom(separately developed) authentication system have been used to identify the uniqueness of the end user. Based on the system, authentication and authorization are proceeded.

In the recent years、the idea of Web identity has become general, thus the number of web services which authenticates the identity using the authentication system of Facebook, Google, Twitter and so on has increased. However, sometime you encounters problem of the Web identity system. For example, When you develop a mobile application which intends to be used for the limited number of users, you have to manage user identity by your own authentication system as Web Identity is available for all who owns the account of the public web services.

So far, such authentication cannot be handled by Amazon Cognito, but this update changed the situation.

How to use

The new feature enables to issue temporary AWS Credentials by using your own authentication system thus you need to implement a server-side authentication system. You can get Identity ID and Open ID connect token, which are issued by Cognito and temporary AWS credential can be received via STS by using these IDs.

cognito-developer-identity

Requirement

You need to use the latest version of AWS SDK. example version are below:

  • AWS SDK for Java 1.8.11
  • AWS SDK for iOS 2.0.8
  • AWS SDK for Android 2.1.0

Usage

the following steps are required to get AWS Credentials using Cognito.

  1. Get access token of some Web Service
  2. Request Open ID token to your authentication system by passing the access token and Identity Pool ID as parameters.
  3. Get Cognito credentials based on the open ID Token acquired in the previous step.
  4. Now you can access AWS services by the Cognito credentials.

Implementation

I chose Java as a Server-Side language、Android as client app.

Server-Side(Java)

As a first step, let's take a look at server-side implementation. You can get Open ID Connect Token by calling GetOpenIdTokenForDeveloperIdentity API. You can get Open ID connect Token tied with the request which includes userID issued by the original authentication system. It might be good to implement with the server side login logic and return the response toward client as an additional information.

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	BasicAWSCredentials credentials = new BasicAWSCredentials("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY");
	AmazonCognitoIdentityClient client = new AmazonCognitoIdentityClient(credentials);
	
	// Set user ID to CustomProvider
	HashMap<String, String> map = new HashMap<String, String>();
	map.put("CustomProvider", "userId");
	
	// Get Open ID Connect Token
	GetOpenIdTokenForDeveloperIdentityRequest tokenRequest = new GetOpenIdTokenForDeveloperIdentityRequest();
	// Identity Pool must be created prior to the execution.
	tokenRequest.setIdentityPoolId("us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx");
	// set login inf
	tokenRequest.setLogins(map);
	// Duration of generated open ID connect token
	tokenRequest.setTokenDuration(1000L);
	GetOpenIdTokenForDeveloperIdentityResult developerIdentityResult = client.getOpenIdTokenForDeveloperIdentity(tokenRequest);

	// return Open ID Connect Token
	response.getWriter().write(developerIdentityResult.toString());
}

Client app implementation (Android)

You need to create custom provider class which extends AWSAbstractCognitoIdentityProvider abstract class. CognitoCachingCredentialsProvider used in this class handles your original provider as a identity provider for Cognito. refresh() method returns open ID connect token provided by the server program implemented formerly. getProviderName() method returns the name of your original service provider.

public class CognitoTask extends AsyncTaskLoader<Boolean> {

  private static final String accountId = "xxxxxxxxxxxx";
  private static final String poolId = "us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
  private static final String unauthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleUnauth_DefaultRole";
  private static final String authArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleAuth_DefaultRole";
  
  public CognitoTask(Context context) {
    super(context);
    forceLoad();
  }

  @Override
  public Boolean loadInBackground() {
    
    // generate STS instance
    AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(new AnonymousAWSCredentials());
    sts.setRegion(Region.getRegion(Regions.US_EAST_1));
    
    // generate CustomProvider
    AWSCognitoIdentityProvider provider = new CustomProvider(accountId, poolId);
    // Set userID
    HashMap<String, String> logins = new HashMap<String, String>();
    map.put("CustomProvider", "userId");
    provider.setLogins(logins);
    
    // receive temporary AWS credential
    CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(getContext(), provider, unauthArn, authArn, sts);
    AWSSessionCredentials credentials = credentialsProvider.getCredentials();
    
    Log.d("credentials" , "accessKey:" + credentials.getAWSAccessKeyId());
    Log.d("credentials" , "secretKey:" + credentials.getAWSSecretKey());
    
    return true;
  }
  
  
  /**
   * Implementation class of AWSAbstractCognitoIdentityProvider
   */
  public class CustomProvider extends AWSAbstractCognitoIdentityProvider {

    public CustomProvider(String accountId, String identityPoolId) {
      super(accountId, identityPoolId);
    }

    @Override
    public String refresh() {
      return getOpenIDConnectToken();
    }

    @Override
    public String getProviderName() {
      // return provider name (decide any proper value)
      return "CustomProvider";
    }
  }
  
  /**
   * get Open ID connect token
   * @return Open ID Connect Token
   */
  private String getOpenIDConnectToken() {
    String idToken = "";
    return idToken;
    
  }
}

You may get the temporary AWS credential by the above step.

Client app implementation (iOS)

In addition to Android, I implemented iOS. The basic process of implementation is same as Android. You need to create the subclass of AWSAbstractIdentityProvider , then implement getIdentityId: method and refresh: method. The return value is BFTask, which is a task object of Bolts. Bolts You can call any callback by using BFTaskCompletionSource.

CustomIdentityProvider.h

#import <AWSCore.h>

@interface CustomIdentityProvider : AWSAbstractIdentityProvider

@property (nonatomic, strong) AWSCognitoIdentity *cib;
@property (nonatomic, strong) NSString *accountId;
@property (nonatomic, strong) NSString *identityPoolId;
@property (nonatomic, strong) NSString *identityId;
@property (nonatomic, strong) NSString *token;

@end

CustomIdentityProvider.m

#import "CustomIdentityProvider.h"

@implementation CustomIdentityProvider

@synthesize accountId = _accountId;
@synthesize identityPoolId = _identityPoolId;
@synthesize identityId = _identityId;
@synthesize token = _token;

- (BFTask *)getIdentityId
{
    if (self.identityId) {
        // return BFTask if identityId is provided
        return [BFTask taskWithResult:nil];
    } else {
        // exec refresh if identityId is not provided
        return [[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) {
            if (!self.identityId) {
                return [self refresh];
            }
            return nil;
        }];
    }
}

- (BFTask *)refresh
{
    // get Open ID connect Token by the API request to authentication server
    NSURL *url = [NSURL URLWithString:@"YOUR_SERVER_API_URL"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
    [NSURLConnection sendAsynchronousRequest:request
                                       queue:[NSOperationQueue mainQueue]
                           completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
    {
        if (data) {
            // retrieve identity ID and Open ID connect Token from the response.
            NSError *jsonError = nil;
            NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data
                                                                   options:NSJSONReadingMutableLeaves
                                                                     error:&jsonError];
            NSLog(@"result: %@", result);
            self.identityId = result[@"identityId"];
            self.token = result[@"token"];
            ;
        } else {
           NSLog(@"error: %@", error);
            ;
        }
    }];
    return ;
}

@end

At last, the following code needs to be implemented. When you use iOS, set AWSServiceConfiguration to AWSServiceManager to retrieve AWS credentials.

static NSString* const kAccountId = "xxxxxxxxxxxx";
static NSString* const kIdentityPoolId = "us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
static NSString* const kUnauthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleUnauth_DefaultRole";
static NSString* const kAuthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleAuth_DefaultRole";

// generate CustomIdentityProvider
CustomIdentityProvider *customIdentityProvider = [CustomIdentityProvider new];
customIdentityProvider.accountId = kAccountId;
customIdentityProvider.identityPoolId = kIdentityPoolId;
customIdentityProvider.logins = @{@"CustomProvider" : @"userId"};

// generate AWSCognitoCredentialsProvider
AWSCognitoCredentialsProvider *provider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
                                                                                   identityProvider:customIdentityProvider
                                                                                      unauthRoleArn:kUnAuthArn
                                                                                        authRoleArn:kAuthArn];

// register AWS credential
AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:AWSRegionUSEast1
                                                                      credentialsProvider:provider];
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;

// Authentication
[[provider getIdentityId] continueWithSuccessBlock:^id(BFTask *task){
    NSString* cognitoId = provider.identityId;
    NSLog(@"cognitoId: %@", cognitoId);
    NSLog(@"logins: %@", provider.logins);
    return nil;
}];

Summary

At the moment of the release of Amazon Cognito, some developer said "Why Twitter isn't supported?". But I think this new feature of Cognito can spread the possibility of the mobile app further.