1
votes

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.

  1. The user runs the app for the first time. He gets IdentityA as unauthenticated identityId, logins empty. He saves some data on DatasetA.
  2. 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.
  3. The user logs out. He gets unauthenticated IdentityB, logins/dataset empty. He saves some data on DatasetB.
  4. 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...

  1. Save on developer side database the user's token currently registered on Cognito (tokenA) and the first identityId that will not change (identityA)
  2. Call GetOpenIdTokenForDeveloperIdentity(IdentityB, DEVDOMAIN:TokenB) to temporarily link IdentityB and TokenB
  3. Then call MergeDeveloperIdentities(TokenB to TokenA) (I hope this will merge the datasets...)
  4. Maybe call DeleteIdentities(IdentityB) as it will never be used?
  5. 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
1
Some follow up questions here. 1.) Where are these "TokenA" and "TokenB" coming from? Are these the tokens from the response of GetOpenIdTokenForDeveloperIdentity call? 2.) On user sign out, are you clearing the user identity id manually on the client?Chetan Mehta
1) TokenA/TokenB are tokens generated by our service. These are access tokens (generated randomly) to give access to the developer side server's API, they are unique, they expire in 2 weeks, they change every time the user logs in. Maybe I shouldn't use this as Cognito logins[DEVDOMAIN]? 2) On sign out, I'm calling [[AWSCognito defaultCognito] wipe] then getIdentityId to get a new one. I'll edit the question with the exact code.Runo Sahara

1 Answers

1
votes

You should not use a randomly generated token which changes on sign in as a user identifier, if you want the user to always have the same identity id. We identify the user uniquely based on the identifier you pass us. For example, you can use the user name in the logins map when you call GetOpenIdTokenForDeveloperIdentity from your backend.

If we already have an identity generated for the identifier and that identity does not match the identity you passed in the request, we will merge these two along with their datasets.

More details about developer authenticated flow can be found in our dev guide.