In my app, all the users start with an identityId from Cognito (directly got from Cognito/GetId from the app) and the authenticated users update the identityId through developer authentication (GetOpenIdTokenForDeveloperIdentity with old IdentityId set via developer side server). The authenticated users can logout/login anytime, but the developer side token changes once logged out or when the token expires.
What's the best way to manage to keep the same identityId for a user in the conditions above?
For instance: when the same user logs out then logs in.
- The user runs the app for the first time. He gets IdentityA as unauthenticated identityId, logins empty. He saves some data on DatasetA.
- The user logs in with USER/PASSWORD. He gets {"DEVDOMAIN":"TokenA"} as logins. GetOpenIdTokenForDeveloperIdentity(IdentityA, DEVDOMAIN:TokenA) links them, the user is authenticated with IdentityA and DatasetA is kept.
- The user logs out. He gets unauthenticated IdentityB, logins/dataset empty. He saves some data on DatasetB.
- The user logs in with USER/PASSWORD. He gets {"DEVDOMAIN":"TokenB"} as logins.
What I need in this case is to associate to this user:
- The latest token TokenB.
- An IdentityId to access AWS as the same user.
- DatasetB merged onto DatasetA.
Calling GetOpenIdTokenForDeveloperIdentity(IdentityB, DEVDOMAIN:TokenB) means creating a new user right? So should I...
- Save on developer side database the user's token currently registered on Cognito (tokenA) and the first identityId that will not change (identityA)
- Call GetOpenIdTokenForDeveloperIdentity(IdentityB, DEVDOMAIN:TokenB) to temporarily link IdentityB and TokenB
- Then call MergeDeveloperIdentities(TokenB to TokenA) (I hope this will merge the datasets...)
- Maybe call DeleteIdentities(IdentityB) as it will never be used?
- Then tell the app "you are IdentityA"
Is there a better way?
Thanks.
EDIT:
TokenA / TokenB are access tokens for the developer side server API. They change every time the user logs in, they expire in 2 weeks.
The code I use for log out:
// Code in Logout
AWSCognitoCredentialsProvider* credentialsProvider = [AWSServiceManager defaultServiceManager].defaultServiceConfiguration.credentialsProvider;
[credentialsProvider clearKeychain];
[[AWSCognito defaultCognito] wipe];
After login/logout, I'm updating the user's identityId this way:
AWSCognitoCredentialsProvider* credentialsProvider = [AWSServiceManager defaultServiceManager].defaultServiceConfiguration.credentialsProvider;
NSString* previousIdToken = credentialsProvider.logins[IDPROVIDER_OURSERVICE];
if (previousIdToken == nil || ![idToken isEqualToString:previousIdToken])
{
if (idToken.length == 0)
{
// Logout
credentialsProvider.logins = @{ };
return [credentialsProvider getIdentityId];
}
else
{
// Login/Update
credentialsProvider.logins = @{ IDPROVIDER_OURSERVICE:idToken };
return [credentialsProvider refresh];
}
}
return [AWSTask taskWithResult:idToken];
I'm also running the following code on startup, to clear credentials when users reinstall the app (as uninstall = logout in my app)
// Initialization code
AppIdentityProvider* appIdentityProvider = [[AppIdentityProvider alloc] initWithRegionType:AWSRegionUSEast1
identityId:nil
accountId:COGNITO_ACOUNT_ID
identityPoolId:COGNITO_IDENTITY_POOL_ID
logins:logins];
AWSCognitoCredentialsProvider* credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
identityProvider:appIdentityProvider
unauthRoleArn:nil
authRoleArn:nil];
if ([AppContext sharedInstance].appInitRunCount == 1)
{
// Restart as guest after an uninstall
NSLog(@"=== AppUserManager: First run: clearing keychain");
[[AWSCognito defaultCognito] wipe];
[credentialsProvider clearKeychain];
}
AWSServiceConfiguration* configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1
credentialsProvider:credentialsProvider];
[AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;
[[credentialsProvider getIdentityId] continueWithSuccessBlock:^id(AWSTask *task) {
[self testCognito];
return nil;
}];
My custom identity provider looks like this:
@interface AppIdentityProvider : AWSAbstractCognitoIdentityProvider
// ...
@end
@interface AppIdentityProvider ()
@property (nonatomic, strong, readwrite) NSString *token;
@end
@implementation AppIdentityProvider
@synthesize token = _token;
-(id)initWithRegionType:(AWSRegionType)regionType
identityId:(NSString*)identityId
accountId:(NSString*)accountId
identityPoolId:(NSString*)identityPoolId
logins:(NSDictionary*)logins
{
self = [super initWithRegionType:regionType
identityId:identityId
accountId:accountId
identityPoolId:identityPoolId
logins:logins];
if (self == nil)
return nil;
return self;
}
-(BOOL)isAuthenticatedWithOurService
{
return self.logins != nil && [self.logins objectForKey:IDPROVIDER_OURSERVICE] != nil;
}
- (AWSTask *)getIdentityId
{
if (self.identityId != nil)
return [AWSTask taskWithResult:self.identityId];
if (![self isAuthenticatedWithOurService])
return [super getIdentityId];
return [[AWSTask taskWithResult:nil] continueWithBlock:^id(AWSTask *task) {
if (self.identityId != nil)
return [AWSTask taskWithResult:self.identityId];
return [self refresh];
}];
}
- (AWSTask *)refresh
{
AWSTaskCompletionSource *source = [AWSTaskCompletionSource taskCompletionSource];
if (![self isAuthenticatedWithOurService])
return [super getIdentityId];
ApiRequest* authApi = [[ApiRequestManager sharedInstance] generateAuthApiByIdToken:self.logins[IDPROVIDER_OURSERVICE]];
[authApi requestAsyncCompletionHandler:^(ApiRequest *request)
{
NSDictionary *response = request.response;
if ([request hasSucceeded] && [[response valueForKey:@"result"] intValue] == 1)
{
self.token = [[response valueForKey:@"data"] valueForKey:@"token"];
self.identityId = [[response valueForKey:@"data"] valueForKey:@"identityId"];
[source setResult:self.identityId];
}
else
{
[source setError:[NSError errorWithDomain:@"refresh" code:0 userInfo:response]];
}
}];
return source.task;
}
@end