この記事は公開されてから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.
- Enhanced Identity Support for Amazon Cognito | Amazon Web Services Blog
- Amazon Cognito : Announcing Developer Authenticated Identities | AWS Developer Blog - Mobile
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.
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.
- Get access token of some Web Service
- Request Open ID token to your authentication system by passing the access token and Identity Pool ID as parameters.
- Get Cognito credentials based on the open ID Token acquired in the previous step.
- 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.