Closed npalethorpe closed 6 years ago
If you don't implement the delegate and the refresh token is expired, the SDK doesn't have any way of prompting the user for their username and password again and has no way of issuing new tokens. Did you set your refresh tokens to only be valid for 1 hour when you configured your User Pool in the console? If not, the SDK should be able to refresh without the username/password until the refresh token expires. If so, you will need to call getSession and provide the username/password again.
Thanks for the quick response. Turns out I got my information a little wrong - the times the refresh code comes back as empty it does appear to pick itself back up and continues working. However I left the app unopened (note that I am using the iOS simulator) for likely 17+ hours and when I came back I made the usual getSession call which returns all three tokens however the API gateway just no longer acknowledges the credentials until I log out and log back in.
The API returns an error message of:
@"User: arn:aws:sts::47*********9:assumed-role/Cognito_MyAppUsersUnauth_Role/CognitoIdentityCredentials is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:us-east-1:********6789:91qkshfe93/prod/POST/getcc"
As I mention; if I sign out and back in again then it all springs back into life. The User Pool is setup with a refresh token that expires after 30 days.
I'm hoping someones going to just point out something stupid I'm doing. Below is the code I'm running on app start up to pick up an existing authenticated user and set things back up again:
AWSCognitoIdentityUserPool *pool;
AWSCognitoIdentityUser *user;
AWSServiceConfiguration *serviceConfiguration;
AWSCognitoCredentialsProvider *credentialsProvider;
// Setup the pool
AWSCognitoIdentityUserPoolConfiguration *configuration = [[AWSCognitoIdentityUserPoolConfiguration alloc]
initWithClientId:CognitoIdentityUserPoolAppClientId
clientSecret:CognitoIdentityUserPoolAppClientSecret
poolId:CognitoIdentityUserPoolId];
// The Credentials Provider holds our identity pool which allows access to AWS resources
credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:CognitoIdentityUserPoolRegion identityPoolId:CognitoIdentityPoolId];
// We can then set this as the default configuration for all AWS SDKs
serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:CognitoIdentityUserPoolRegion credentialsProvider:credentialsProvider];
[AWSCognitoIdentityUserPool registerCognitoIdentityUserPoolWithConfiguration:serviceConfiguration userPoolConfiguration:configuration forKey:USER_POOL_NAME];
// Get the pool object now
pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:USER_POOL_NAME];
// Get the user from the pool
user = [pool currentUser];
// Make a call to get hold of the users session
[[user getSession] continueWithBlock:^id(AWSTask<AWSCognitoIdentityUserSession *> *task) {
if (task.error) {
NSLog(@"Could not get user session. Error: %@", task.error);
callback(@[@"", @(CognitoResponseNo)]);
} else {
NSLog(@"Successfully retrieved user session data");
// Get the session object from our result
AWSCognitoIdentityUserSession *session = (AWSCognitoIdentityUserSession *) task.result;
// Build a token string
NSString *key = [NSString stringWithFormat:@"cognito-idp.%@.amazonaws.com/%@", CognitoIdentityUserPoolRegionString, CognitoIdentityUserPoolId];
NSString *tokenStr = [session.idToken tokenString];
NSDictionary *tokens = [[NSDictionary alloc] initWithObjectsAndKeys:tokenStr, key, nil];
CognitoPoolIdentityProvider *idProvider = [[CognitoPoolIdentityProvider alloc] init];
[idProvider addTokens:tokens];
AWSCognitoCredentialsProvider *creds = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:CognitoIdentityUserPoolRegion identityPoolId:CognitoIdentityPoolId identityProviderManager:idProvider];
AWSServiceConfiguration *serviceConfig = [[AWSServiceConfiguration alloc] initWithRegion:CognitoIdentityUserPoolRegion credentialsProvider:creds];
// This sets the default service configuration to allow the API gateway access to user authentication
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = serviceConfig;
// Register the pool
[AWSCognitoIdentityUserPool registerCognitoIdentityUserPoolWithConfiguration:serviceConfig userPoolConfiguration:configuration forKey:USER_POOL_NAME];
[[creds getIdentityId] continueWithBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if (task.error) {
callback(@[[NSString stringWithFormat:@"Could not get identity id: %@", task.error], @(CognitoResponseNo)]);
} else {
callback(@[@"", @(CognitoResponseYes)]);
}
return nil;
}];
}
return nil;
}];
Thanks for any help or advice - I'm new to Objective C and finding it a little overwhelming also understanding the correct authentication flow for Cognito!
I have this same issue, and I believe that I've identified two different problems in here:
These are in getSession() when it is trying to refresh:
Issue 1 When it actually calls this:
return [[self.pool.client initiateAuth:request] continueWithBlock:^id _Nullable(AWSTask<AWSCognitoIdentityProviderInitiateAuthResponse *> * _Nonnull task) {
in AWSCognitoIdentityUser.m, it fails. This is because it signs the request, and the current access token is invalid (expiredToken). The refresh does work if you nil out the requestInterceptors for this call (which you have to do in the debugger - they are set in assignProperties in AWSNetworking.m, from the configuration).
IF you nil the requestInterceptors array out, the initiateAuth succeeds, and it updates the accessToken, refreshToken, and idToken..
Issue 2 When it does return the expiredToken code, it looks like it's trying to test for that:
return [[self.pool.client initiateAuth:request] continueWithBlock:^id _Nullable(AWSTask<AWSCognitoIdentityProviderInitiateAuthResponse *> * _Nonnull task) {
if(task.error){
//If this token is no longer valid, fall back on interactive auth.
if(task.error.code == AWSCognitoIdentityProviderErrorNotAuthorized) {
return [self interactiveAuth];
} else {
return task;
}
}
But the error that is being returned is:
2017/05/03 23:01:38:092 Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=8 "(null)" UserInfo={message=Invalid login token. Token expired: 1493855918 >= 1493850102, __type=NotAuthorizedException}]
And AWSCognitoIdentityProviderErrorNotAuthorized is ~16, not 8. So it won't call the interactiveAuth like it should.
(edited to remove remove 2nd issue, which was mine, and add clarification)
FYI-
I fixed Issue 1, but it's not in a method that can be submitted as a patch. Basically, I exposed client (from AWSCognitoIdentityUserPool) and configuration (from AWSCognitoIdentityProvider). Then I head patched the signature code to explicitly not sign if the request is AWSCognitoIdentityProviderService.InitiateAuth.
The problem is that the requestInterceptors isn't exposed at a level that will let me to do the above in a clean manner, and there is no way to override them on a per-request basis.
This now refreshes properly.
I suspect Issue 2 is a case that happens if the refresh token has expired, and it might be the legitimate error condition in that case.
If an AWS engineer would suggest a method to patch this, I'll definitely do the work here. My possible thoughts:
1) Make AWSSignatureV4Signer check to see if X-Amz-Target is AWSCognitoIdentityProviderService.InitiateAuth, and if so, not sign. (Simplest, but ugly)
2) Add a boolean value to AWSNetworkingRequest to sign or not; default is true. This seems like it would change a lot of things. I don't like this one, because it implies knowing what the requestInterceptors are.
3) Provide a method to override requestInterceptors on a AWSNetworkingRequest basis. There appears to be precedent for this in assignProperties in AWSNetworkingRequest. Something like:
if(!self.requestInterceptors && configuration.requestInterceptors) {
self.requestInterceptors = configuration.requestInterceptors;
}
Then, in this one call, you would explicitly set the requestInterceptors to not include the signing request. This is probably the cleanest implementation.
Hi @rmartell, @npalethorpe
Sorry for the delay, I missed the updates to this issue. It appears the reason that both your issues are happening are because you are specifying a credentialsProvider in the AWSServiceConfiguration
for your AWSCognitoIdentityUserPool
object. This is causing the requests to be signed, and to make calls to Cognito Identity using an expired token during the refresh. Although you need a AWSServiceConfiguration
like this for most services, AWSCognitoIdentityUserPool
is different. You should have a separate AWSServiceConfiguration
specifically for your AWSCognitoIdentityUserPool
object with a nil
credentials provider as all the calls are unauthenticated or authenticated with an access token, not SigV4 credentials.
Here is an example:
AWSServiceConfiguration *serviceConfiguration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:nil];
More details on the correct configuration here:
And here:
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
This issue has been automatically closed because of inactivity. Please open a new issue if are still encountering problems.
I've implemented AWS SDK Objective C into my project and all appears to be working correctly, however after an hour of non use, getSession will return an object back containing all but the Refresh Token (which is expired at this point according to the expirationTime property).
My setup does not use the delegate calls as it just doesn't fit into the flow of my app model - so am I missing something or is this a bug? I can see that getSession is refreshing the tokens whilst I am actively using my app, its just whilst I am idle for that hour period.
The result is that the user stays logged in but calls to the API Gateway and other items using the defaultServiceConfiguration will fail until I log the user out and get them to sign back in.
I haven't tried this with the Android Java sdk yet but I'm going to presume I'm going to run into the same issue there too!
Thanks for any help.