So I spent ages getting a custom login provider to work. I have a class BYOIProvider extending AWSAbstractCognitoIdentityProvider based on some AWS sample. This was all working fine. However, upgrading to 2.4.0 the AWSAbstractCognitoIdentityProvider is no longer available? Do I have to rewrite this code now?
Downloading the sample code again, they still seem to use DeveloperAuthenticatedIdentityProvider : AWSAbstractCognitoIdentityProvider. So not sure what to do here, I guess I just have to revert to the old 2.3.6, as getting this custom identity to work was very complex in the first place.
YES, same issue. the 2.4.0 version will break my production app.
I have a customerSignin Provider based on AWSEnhancedCognitoIdentityProvider
@interface CustomIdentityProvider : AWSEnhancedCognitoIdentityProvider
I hope there is a transition tutorial for the removed class.
Thank you!
Same issue, just updated via cocoapods to 2.4.0, I used the Cognito sample app to build off, I now have the following errors.
Fatal: Value of type 'AWSCognitoCredentialsProvider' has no member 'refresh'
https://github.com/awslabs/aws-sdk-ios-samples/blob/master/CognitoSync-Sample/Swift/CognitoSyncDemo/AmazonClientManager.swift#L130
Warning: 'logins' is deprecated: Use 'AWSIdentityProviderManager' to provide a valid logins dictionary to the credentials provider.
In 2 Lines:
https://github.com/awslabs/aws-sdk-ios-samples/blob/master/CognitoSync-Sample/Swift/CognitoSyncDemo/AmazonClientManager.swift#L118
It would be great if the samples could be updated to reflect the changes in 2.4.0.
https://github.com/awslabs/aws-sdk-ios-samples
https://github.com/awslabs/aws-sdk-ios-samples/tree/master/CognitoSync-Sample
I had a look at the changelog, It mentions the deprecated logins issue, but nothing about the Value of type 'AWSCognitoCredentialsProvider' has no member 'refresh' Fatal error.
https://github.com/aws/aws-sdk-ios/blob/master/CHANGELOG.md
@yosuke-matsuda @aws-dpt @behrooziAWS Can someone spare a few mins to update the samples to reflect the new changes when you get a chance?
You can take a look at the implementation of AWSCognitoCredentialsProvider and its use of AWSCognitoCredentialsProviderHelper as a reference.
We are preparing the sample app updates, and it should be available soon. When we update our samples, we'll update this thread. Thanks.
I can't seem to figure this out. Where do I implement my previous refresh() method? Do I now have to build a AWSCognitoIdentityProviderRefreshTokensRequest instead? Hopefully your samples will help.
It's bit daunting that after spending so much time to get custom logins working with Lambda a point update has changed everything it seems. Especially considering this is something the competition is offering with zero implementation and is something almost every app will need, as relying on 3rd party logins is not feasible for a real life app.
breaking changes like this should not be a point update 👎. Dealing with this same issue myself. What makes it worse is documentation of this breaking change shouldve gone up with the 2.4.0 release :(.
Normal practice would have been to leave the deprecated method in there still working but flagged as depcricated. To sink the whole sdk like this I don't think is acceptable unless you're on a 0.9 release cycle.
Did anybody manage to do this? Cause i'm in need to implement it and i can't seem to figure out which methods should i implement of the provider, maybe i should return NO in the isAuthenticated method and then my getIdentityId will be called?
@guillecom you might want to go with the new user pools, although it's in beta only. https://mobile.awsblog.com/post/TxGNH1AUKDRZDH/Announcing-Your-User-Pools-in-Amazon-Cognito
Thanks for the advice, here is one that might shed some light on the original issue:
The class we need to implement is
@protocol AWSCognitoCredentialsProviderHelper <AWSIdentityProvider, AWSIdentityProviderManager>
which the implementation of the identity provider manager has this method
@protocol AWSIdentityProviderManager <NSObject>
/**
* Each entry in logins represents a single login with an identity provider.
* The key is the domain of the login provider (e.g. 'graph.facebook.com') and the value is the
* OAuth/OpenId Connect token that results from an authentication with that login provider.
*/
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins;
I think that this is our new refresh method (altought i never implemented a refresh method in the first place :P)
I was right, i just managed to implement this, if you implement the logins method of the AWSCognitoCredentialsProvider, which will set the identityId and also return an AWSTask with a dictionary containing [self.indentityProviderName : <THE OPEN ID RETURNED> ], everything works
Remember not to overload identityProviderName (it has to be something like cognito-identity.amazon.etc)
And to overload the identityPoolId to be the one you have setup
7 days already and no proper documentation for how to reimplement something that was broken. Considering how huge this SDK you can imagine how agonizing this can get. I wasted various hours already on this to no avail. Rolling back to pre 2.4.0 and scrutinizing every minor update on this repo.
I agree with everyone else here. Ive spent countless hours debugging and googling and reading aws documentation, until i found this thread. If you make an update like this , atleast provide documentation for something like this , custom developer identity is not a small thing to skip documenting.
Whats even more fun is googling around every freaking website to find the older version . .. . great
I want to take a little bit of time to explain the version numbering convention we use in our SDK.
The incremented first digit indicates a total overhaul of the SDK. There is almost no backward compatibility, and you need to completely rewrite your app. This happened when we jumped from 1.x.x to 2.x.x.
The second digit indicates minor breaking changes. For example, from 2.2.x to 2.3.0, we started including bitcode. Because Xcode 6 does not support bitcode well, we bumped the required version of Xcode from 6 to 7. From 2.3.x to 2.4.0, we dropped the support for iOS 7 and updated the credentials provider protocols (the custom login provider is one implementation of credentials provider). One exception to this rule is that we do not bump the second digit version when we make breaking changes to beta labeled components (e.g. AWSS3TransferUtility).
We release updated API docs with the SDK update. @guillecom used them to implement the credentials provider on his own.
http://docs.aws.amazon.com/AWSiOSSDK/latest/Protocols/AWSCredentialsProvider.html
http://docs.aws.amazon.com/AWSiOSSDK/latest/Protocols/AWSIdentityProvider.html
http://docs.aws.amazon.com/AWSiOSSDK/latest/Protocols/AWSIdentityProviderManager.html
http://docs.aws.amazon.com/AWSiOSSDK/latest/Protocols/AWSCognitoCredentialsProviderHelper.html
We are still working on more detailed documentation with sample apps. They will be available shortly. If API documentation is not enough, and you need more help, please watch out for 2.x.0 releases and evaluate thoroughly before jumping on it right away since they contain some breaking changes.
We understand this is not Semantic Versioning 2.0.0 compliant, and this may be one of the reasons some people got confused. We will see if we can adopt Semantic Versioning in our SDK.
Thanks,
@yosuke-matsuda @guillecom I'm no being able to get as far as login in and getting my token and cognito ID. But once I try and do something like query a table it comes back with error:
> [Error] AWSCredentialsProvider.m line:563 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke.345 | Unable to refresh. Error is [Error Domain=com.amazonaws.AWSServiceErrorDomain Code=11 "(null)" UserInfo={Type=Sender, Message=Not authorized to perform sts:AssumeRoleWithWebIdentity, Code=AccessDenied, __text=(
> "\n ",
> "\n ",
> "\n ",
> "\n "
> )}]
> Optional("The operation couldn’t be completed. (com.amazonaws.AWSServiceErrorDomain error 11.)")
Before I never had to deal with sts:AssumeRoleWithWebIdentity ? Is this something I need to add now?
@yosuke-matsuda Might it be easier to get this to work being it on the mobile hub code example where there is a AWSMobileHubHelper that has a AWSSignInProvider.
Looking at that code though, I'm not sure how to implement the login() method in order to get the AWSIdentityManager.defaultIdentityManager().loginWithSignInProvider(signInProvider, completionHandler: {(result: AnyObject?, error: NSError?) to be called back?
Same problem here!
If I want to add AWSCognitoIdentityProvider pod, it asks me to update AWScore to 2.4.0. If I update to 2.4.0 then I get this thread problem! So I'm in a loop. I can't user AWSCognitoIdentityProvider and I can't update. Please solve this problem asap and update samples! Everybody would appreciate it! Thanks
Just pushed an updated Cognito Sync Demo app to a branch. You should take a look at the implementation of DeveloperAuthenticatedIdentityProvider. This branch uses only Developer Authenticated Identities to highlight the usage of DAI. However, you can simply provide identityProviderManager to DeveloperAuthenticatedIdentityProvider to add any other identity providers.
This Cognito Sync sample app seems to be a little behind of the latest AWS SDK updates, and there are many areas where it overlaps with AWS Mobile Hub generated sample apps. We will try to integrate AWSIdentityManager from AWS Mobile Hub to this sample app as @helloniklas suggested. When it is done, we'll update the master branch. Thanks.
@yosuke-matsuda So I now get the token and the identiyID fine, however, once I try and make a dynomdb call I then get an error saying
Not authorized to perform sts:AssumeRoleWithWebIdentity, Code=AccessDenied
This does not happen when running 2.3.6 with the same dev identity providers. Not sure if there's some clash with the new identity pools somehow, as I'm not aware of this sts:AssumeRoleWithWebIdentity and why it's being denied when running 2.4.0 and not while running 2.3.6 ?
@yosuke-matsuda I did create in AmazonClientManager.swift a new function called customLogin:
func customLogin() {
let userPoolConfiguration: AWSCognitoIdentityUserPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(
clientId: "3ds8p7eog7rddXXXXXXXXXXX",
clientSecret: "cu3n1h7kb4n7j72q4XXXXXXXXXXXXXXXXXXX",
poolId: "us-east-1_onzXXXXXX")
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithUserPoolConfiguration(
userPoolConfiguration,
forKey: "UserPool")
let pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
self.completeLogin(["us-east-1_onzXXXXXXX" : pool.token()])
let phone: AWSCognitoIdentityUserAttributeType = AWSCognitoIdentityUserAttributeType()
phone.name = "phone_number"
phone.value = "+44434343434"
let nickname: AWSCognitoIdentityUserAttributeType = AWSCognitoIdentityUserAttributeType()
nickname.name = "nickname"
nickname.value = "Hello"
let email: AWSCognitoIdentityUserAttributeType = AWSCognitoIdentityUserAttributeType()
email.name = "email"
email.value = "[email protected]"
pool.signUp("Hello", password: "Rasdfgf123456", userAttributes: [email, nickname], validationData: nil) .continueWithBlock({ (task: AWSTask!) -> AnyObject! in
if ((task.error) != nil) {
print("Error: \(task.error)")
}
if ((task.result) != nil){
print("User Created")
}
return nil
})
}
but I'm getting this error compile error:
[Error] AWSCredentialsProvider.m line:423 | __73-[AWSCognitoCredentialsProvider getCredentialsWithCognito:authenticated:]_block_invoke | GetCredentialsForIdentity failed. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=9 "(null)" UserInfo={__type=ResourceNotFoundException, message=Identity 'us-east-1:a2d8ee68-34aa-413d-9a09-38a57e820XXX' not found.}]
It looks like is trying to find this identityId in my Federated Identities panel, but since it doesn't exists, is shows this error. What it missing in order to have this identity Id created?
@yosuke-matsuda If I change the Trust Relationship to
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "*"
}
it does seem to work now... however, not sure what these trust thing is all about as I didn't need that under 2.3.6
@helloniklas Are you passing unauthRoleArn or authRoleArn to the credentials provider? For Enhanced Flow, you do not need to pass these values (See Authentication Flow for more details). If you are passing Role ARNs, can you remove them and try again?
@Stradix The error you are encountering does not seem to be related to the logins dictionary. You can create a new thread with detailed steps to reproduce the issue. AmazonClientManager.swift alone is not enough to reproduce the issue.
@yosuke-matsuda right, that seems to work now, missed that these were set to nil in your example.
@yosuke-matsuda However, I'm now running in to some other issues I didn't have with 2.3.6. If I first log in with DeveloeprAuthenticated ID, then logout, and try and log in with Facebook, it won't work until I completely shut the app down and login. Seems the logout doesn't really work properly. Before I could clear the logins but now with 2.4.1 I set keychain etc to nil and
AWSCognito.defaultCognito().wipe()
if let credentialsProvider = AWSServiceManager.defaultServiceManager().defaultServiceConfiguration.credentialsProvider as? AWSCognitoCredentialsProvider {
credentialsProvider.clearKeychain()
}
But this somehow doesn't work properly. Even after loging out then authenticating with Facebook, when I then try and do a request, it still tries to refresh the DeveloperAuthenticated thing.
And vice versa, if I first login Facebook, then logout, when then loging in with DeveloperAuth, it still tried to refresh the Facebook manager, although I've set the AWSCognitoCredentialsProvider to use the DeveloperAuth.
@yosuke-matsuda I also now get an error the first time the app is installed and try to login,
AWSiOSSDK v2.4.1 [Debug] AWSInfo.m line:122 | -[AWSServiceInfo initWithInfoDictionary:checkRegion:] | Couldn't read the region configuration from Info.plist for the client. Please check your `Info.plist` if you are providing the SDK configuration values through `Info.plist`.
2016-05-04 16:16:04.594 Duoo[11194:3028281] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'The service configuration is `nil`. You need to configure `Info.plist` or set `defaultServiceConfiguration` before using this method.'
this causes the app to crash. It then works the second time around. Seems 2.4.1 is very flakey? Or I'm missing something in the implementation.
@helloniklas The reason why you needed to open up your trust relationship above was likely because you were passing in roles for a different identity pool. Since the pool didn't match what was in the amr, requests were getting rejected. Now that you are not passing in roles (you've set them to nil) it is using the correct roles that are associated with your pool. You should always lock down your trust relationship, otherwise anyone using Cognito Identity can assume your roles.
Without seeing what you are doing in your IdentityProviderManager, it is difficult to tell why you are still authenticating as dev auth after logging out. Do you pivot to setting the Facebook token in the logins map after logging out and is the Facebook token the only item in your logins map?
With your app crash, are you correctly setting the defaultServiceConfiguration? See Step 4 for an example.
@behrooziAWS The pool was the same, I only have one. However, passing nil seems to work.
As for the crash I'm now getting on first login. I do set the things as you suggest. But, it seems when I then do a dynamoDBObjectMapper.query after getting the credentialsProvider.getIdentityId().continueWithBlock it crash bacuse the dynamoDB service thing hasn't got the right things set yet? Which is odd, didn't happen before. If I then try and login again after the crash, it works. So, it seems the defaultServiceConfiguration that I set is not being picked up by dynamoDB?
self.identityProvider = BYOIProvider()
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, unauthRoleArn: nil, authRoleArn: nil, identityProvider: self.identityProvider!)
credentialsProvider.invalidateCachedTemporaryCredentials()
let configuration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
@behrooziAWS and how to I now set the logins map? I thought logins had been deprecated?
Does your BYOIProvider only implement AWSIdentityProvider? In order to support multiple login providers, you need to initialize your AWSCognitoCredentialsProvider with a AWSIdentityProviderManager to manage the logins map and pivot between providers when you switch. The logins map in AWSIdentityProviderManager is what I am referring too. We will look into the issue you are having with object mapper not having the defaultServiceConfiguration further.
@behrooziAWS my BYOProvider() is a AWSCognitoCredentialsProviderHelper and implements
override func getIdentityId() -> AWSTask
func logout()
override func token() -> AWSTask
However, I do not necessarily need the logins to merge. I.e. if user is logged out and logs in with Facebook, it can be a new user, which is what I had working all fine under 2.3.6. Reason for this is I have loads of legacy users I migrated from Parse.
So, this whole thing seems a bit of a mystery. It's odd that the second time around, after a crash, it does have the defaultServiceConfiguration.
What would really make this easier if is someone could provide a clean sample of how to implement the equivalent of your AWSFacebookSignInProvider that is used in the generated sample app of yours. In that sample app there are no hints as to how to implement your own provider. The other CognitoSyncSample you have that was updated I find a bit messy to follow, and it's also mixed with loads of objective-C. Under 2.3.6 it was possible to get it all working fine, but now I have all sorts of issues, as this crash.
@behrooziAWS @yosuke-matsuda Also, the CognitoSyncSample doesn't seem to implement the - (AWSTask
It would be great to get the Cognito developer authentication example in Swift too
If I want to use developer authentication, which class do I need to extend now? AWSCognitoCredentialsProviderHelper?
@behrooziAWS @yosuke-matsuda So I completely scrapped my previous Swift implementation and just use the CognitoSyncDemo (modified to call my lambda auth backend). It now does work fine to log in.
But, if I then logout and try and login with Facebook provider, it won't have it. I have to quit the app then login with Facebook provider and it works.
Not sure how to handle this. As in your sample you're setting the defaultServiceConfiguration in a method, but it seems you can't then change this in the same app run, to logout and in with Facebook instead, it won't pick it up. This is the method I'm referring to:
func initializeClients(logins: [NSObject : AnyObject]?) -> AWSTask? {
print("Initializing Clients...")
AWSLogger.defaultLogger().logLevel = AWSLogLevel.Verbose
let identityProvider = DeveloperAuthenticatedIdentityProvider(
regionType: Constants.COGNITO_REGIONTYPE,
identityPoolId: Constants.COGNITO_IDENTITY_POOL_ID,
providerName: Constants.DEVELOPER_AUTH_PROVIDER_NAME,
authClient: self.devAuthClient,
identityProviderManager: nil)
self.credentialsProvider = AWSCognitoCredentialsProvider(
regionType: Constants.COGNITO_REGIONTYPE,
unauthRoleArn: nil,
authRoleArn: nil,
identityProvider: identityProvider)
let configuration = AWSServiceConfiguration(region: Constants.COGNITO_REGIONTYPE, credentialsProvider: self.credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
return self.credentialsProvider?.getIdentityId()
}
If I in there add a check to see if I should use Facebook, and then use
if logins?[Constants.FB_PROVIDER] != nil {
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: Constants.CognitoIdentityPoolId, unauthRoleArn: nil, authRoleArn: nil, identityProviderManager: FaceBookIdentityManager())
let configuration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
}
It only works if I didn't first log in and out with auth client?
How can I change the service provider with out quitting the app?
The identityProviderManager(which you are currently setting to nil) should be what you use to change the login provider. Instead of attempting to swap out the credentials provider when you switch to Facebook, you should update your identityProviderManager to return the logins map containing just the Facebook key and Facebook token.
@behrooziAWS The problem I get if I do that, is that after logging out, then logging in with Facebook, it does call the identitityProviderManager I supplied, but it also keeps going into DeveloperAuthenticatedIdentityProvider getToken() then causing an error.
@behrooziAWS Problems seems to lie in your samples code call to
credentialsProvider?.getIdentityId()
As when this happens, it uses the DeveloperAuthenticatedIdentityProvider
@behrooziAWS It just seems like it's not possible to logout and have it forget what it previously was logged in with, event if the keychain is cleared, so it should be back to how it was to start with.
@helloniklas Ok, we'll look into this in more detail.
@behrooziAWS Thanks, would be helpful if the CognitoSyncSample was a bit more flexible, and easier to modify to use Facebook and Twitter as well.
@behrooziAWS What works for me now is to do an exit(0) and force the app to quit after logout from Cognito. Then I'm able to login again with a different provider. Not the most elegant solution though.
@roymckenzie Yes that is the class you use. See the CognitoSyncSample in this branch for an example.
@helloniklas We discovered one issue with the sample which may be causing the call to getIdentityId issue. This line should be changed to return [super token]; We are doing some more testing with that change and will push it once we have verified it works when we test with Facebook.
@behrooziAWS Hurray, that's done it. I've changed that line and I can now log in and out with different providers. Thanks!
@yosuke-matsuda
Since the other issues have been closed/ marked as duplicate, I'll have to ask here because I don't know of a workaround for the Facebook integration. This thread seems to lean towards custom developer identity providers. I don't have a need for one.
From the changelog
logins dictionary of AWSCognitoCredentialsProvider is now deprecated. Use AWSIdentityProviderManager to provide login providers' credentials. See AWSCredentialsProvider.h and AWSIdentityProvider.h for more details.Can you show us how you "Use AWSIdentityProviderManager to provide login providers' credentials" as described in the changelog?
Here's what I currently use, and I get the deprecation warning:
self.credentialsProvider = AWSCognitoCredentialsProvider(
regionType: Constants.COGNITO_REGIONTYPE,
identityId: nil,
accountId: nil,
identityPoolId: Constants.COGNITO_IDENTITY_POOL_ID,
unauthRoleArn: Constants.COGNITO_UNAUTH_ROLE_ARN,
authRoleArn: Constants.COGNITO_AUTH_ROLE_ARN,
logins: logins
)
PS. I don't have a custom identityProvider in case you suggest a different constructor that requires one.
Probably, you should use - initWithRegionType:identityPoolId:identityProviderManager: instead of the deprecated one. You need to implement AWSIdentityProviderManager to supply the logins dictionary to the credentials provider whenever it needs one. Please note that this dictionary needs to contain valid tokens. See AWSCredentialsProvider.h and AWSIdentityProvider.h for more details.
@yosuke-matsuda
Thanks, the swift code following worked for me:
class CustomIdentityProvider: NSObject, AWSIdentityProviderManager {
var tokens : [NSString : NSString]?
init(tokens: [NSString : NSString]) {
self.tokens = tokens
}
@objc func logins() -> AWSTask {
return AWSTask(result: tokens)
}
}
let customProviderManager = CustomIdentityProvider(tokens: logins!)
self.credentialsProvider = AWSCognitoCredentialsProvider(
regionType: Constants.COGNITO_REGIONTYPE,
identityPoolId: Constants.COGNITO_IDENTITY_POOL_ID,
identityProviderManager: customProviderManager
)
@simoami
Your implementation should work as your previous implementation. However, please remember that when the token from Facebook expires, the AWS SDK starts returning authentication errors.
- logins on AWSIdentityProviderManager is designed to return the logins dictionary asynchronously. We recommend the following approach when implementing - logins.
nil and hasn't been expired, return the logins with the cached token.logins, then cache it and return it.AWSTask so that the error is correctly propagated.@yosuke-matsuda It would be very helpful if you could add Facebook login to your Cognito sample code.
@yosuke-matsuda Thanks for the review feedback. At this point I just want to close the loop on the login and signup lifecycle. Then I can start handling token expiration and apply your other recommendations.
@yosuke-matsuda, like @simoami I'm trying to sign in with facebook, and it would be great if you could help me fix this. I implemented the custom identity provider manager in my project and feed it a facebook token like this
let token = FBSDKAccessToken.currentAccessToken().tokenString
let loginProvider = CustomIdentityProvider(tokens: ["graph.facebook.com": token])
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: AWSRegionType.USEast1, identityPoolId: Constants.identityPoolId, identityProviderManager: loginProvider)
But in the identity pool dashboard, its not authorizing the id with facebook. Am I missing something?
I added
credentialsProvider.getIdentityId()
and it's now logging in.
Is this method some kind of refresh?
Kind of, yes. If you want to wait for the new IdentityID to be fetched before you continue with your code, add .continueWithBlock ….
Okay thanks, I'm trying to understand how this all works. So my intuition is that, I add the facebook token to the credentials provider. CredentialsProvider.getIdentityId()syncs the phone token with AWS, sending the facebook token. AWS checks the facebook token with facebook, authorizes it, and and returns the new identityID to the phone with updated authorization. Is that right?
And this was discussed earlier, but say the user wanted to logout. How should I remove credentials from the credentials provider and unauthorize the user? Thanks again for the help
As fas as I got it, the IdentityID is kind of a meta object which 1.) provides a unique id with the AWS ecosystem and 2.) references to credentials for providers such as Facebook, Twitter, User Pools, etc. If you enable anonymous access for your Cognito Identity Pool the IdentityID has 0 linked logins.
So in your example you might get an id A on initial startup, login to Facebook and keep the same id A, now with one login linked. The fun part starts with multiple devices: If you repeat this now on a new device it gets a new id B and when you authenticate to the same Facebook account, this id B is (kind of) merged into id A. By this you can pull out a new device, do a login and you get access to your existing id A and move on as if the app has ever been on your device. [As far as I've seen it in my app and pulled out of the documentation]
Okay that makes sense. I'm also trying to use user pools, do you know how to get the authenticated token after signing up. I'm calling user.confirmSignUp() followed by user.getSession(username, password). How can I get the proper token to add to credentialsProvider? And to remove credentials should I call credentialsProvider.clearKeychain()then credentialsProvider.getIdentityId()? I really appreciate this help thanks again
yosuke-matsuda do you know when the sample will be updated? I still can't figure out how to implement de Developer Authentication on the SDW 2.4.5. Tanks
@yosuke-matsuda @simoami
Amazon's Developer Guide is still old as you can see below. (last update: 04/19/2016)
NSString *token = [FBSDKAccessToken currentAccessToken].tokenString;
credentialsProvider.logins = @{ @(AWSCognitoLoginProviderKeyFacebook): token };
I'm kinda beginner of iOS programming. So, I can't convert the custom class written in Swift to the one in Objective-C.
class CustomIdentityProvider: NSObject, AWSIdentityProviderManager {
var tokens : [NSString : NSString]?
init(tokens: [NSString : NSString]) {
self.tokens = tokens
}
@objc func logins() -> AWSTask {
return AWSTask(result: tokens)
}
}
let customProviderManager = CustomIdentityProvider(tokens: logins!)
self.credentialsProvider = AWSCognitoCredentialsProvider(
regionType: Constants.COGNITO_REGIONTYPE,
identityPoolId: Constants.COGNITO_IDENTITY_POOL_ID,
identityProviderManager: customProviderManager
)
Could you tell me an example woking in Objective-C? I Need your help. Thank you.
Any updates on this? It's been 3 months and sample apps haven't been updated.
This has been a mess, regardless of the speech given by @yosuke-matsuda in his comment https://github.com/aws/aws-sdk-ios/issues/357#issuecomment-215284491 there are people like me and my team who read the samples that are available publically now, the docs, the blog posts, and we stumble through lost days of work but only for IOS where the API seems to have gone off the rails.
@yosuke-matsuda your message linked above was not helpful and basically said "it's your fault for upgrading". Well, people coming in now that are NOT upgrading are still hurt by the lack of caretaking around this release.
We are still working on more detailed documentation with sample apps. They will be available shortly. If API documentation is not enough, and you need more help, please watch out for 2.x.0 releases and evaluate thoroughly before jumping on it right away since they contain some breaking changes.
Well, you didn't do this for months so more people suffer. Now how many comments from above need to be read, parsed, discarded, sorted through, link scanned, and new errors before something more official happens?
@apatrida you probably better off using something like 0auth instead of AWS own auth system. It's so poorly implemented you'll waste a lot of time on it.
After wasting a couple of days, I _think_ I got developer login providers working on 2.4.5. Here's the basic logic of our code:
First, we created a custom AWSCognitoCredentialsProviderHelper and AWSIdentityProviderManager:
class CognitoCredentialsProviderHelper: AWSCognitoCredentialsProviderHelper {
override var identityPoolId: String {
return "us-east-1:XYZ"
}
private var _identityProviderManager: IdentityProviderManager = {
return IdentityProviderManager()
}()
override var identityProviderManager: AWSIdentityProviderManager? {
return self._identityProviderManager
}
var _token: String!
override func clear() {
super.clear()
self._token = nil
self.identityId = nil
}
func refresh() -> AWSTask! {
return AWSTask(result: nil).continueWithSuccessBlock({ (task: AWSTask!) -> AnyObject! in
let task = AWSTaskCompletionSource()
Alamofire.request(
.GET, "https://www.example.com/auth/cognito", parameters: []
).response { request, response, data, error in
if let error = error {
task.setError(error)
} else {
let json = jsonFromData(data!) // turns data into JSON (we use SwiftyJSON)
self.identityId = json["identityId"].stringValue
self._token = json["token"].stringValue
task.setResult(self._token)
}
}
return task.task
})
}
override func getIdentityId() -> AWSTask {
if (self.identityId != nil) {
return AWSTask(result: self.identityId!)
}
return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
if (self.identityId == nil) {
return self.refresh()
}
return AWSTask(result: self.identityId!)
})
}
override func token() -> AWSTask {
if (self._token != nil) {
return AWSTask(result: self._token!)
}
return AWSTask(result: nil).continueWithBlock({ (task) -> AnyObject! in
if (self._token == nil) {
return self.refresh()
}
return AWSTask(result: self._token!)
})
}
}
class IdentityProviderManager: NSObject, AWSIdentityProviderManager {
@objc func logins() -> AWSTask {
let userIdentifier = getCognitoUserIdentifier() // In our case, we use the current logged in user's id
return AWSTask(result: ["developer-provider-name": userIdentifier])
}
}
With these, we were then able to set the defaultServicesConfiguration to a credentialsProvider using our custom identityProvider:
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: .USEast1,
identityProvider: CognitoCredentialsProvider()
)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = AWSServiceConfiguration(
region: .USEast1,
credentialsProvider: credentialsProvider
)
Please note that we are still testing that everything works as expected, but so far we've been able to get CognitoSync working properly.
Hope this is of some help to someone.
@andresgarza I am trying to integrate Amazon S3 utility with developer authenticated cognito identity provider but when I run the app, it raises error while download file saying identityId can't be nil
So, I logged in overridden getIdentityId() to check whether it is being called or not. And what I found was that it was not being called. I also added logging to token() method and it was being logged after the aws logger said getting identity Id. So it means, even for getting identityId token() was being called. Could you please help me out with this as I am stuck in it for past many days?
Thanks in advance!
Thanks to this forum I'm now able to login in my app with my BYOIProvider and also using a Twitter Account.
But I'm not able to switch from one provider to another after a logout.
I think that the problem is how I init the AWSCognitoCredentialsProvider.
If I use initWithRegionType:identityPoolId:unauthRoleArn:authRoleArn:identityProviderManager:, I'm able to login in Twitter, but not with my own Developer Authentication provider.
Viceversa if I init the AWSCognitoCredentialsProvider with initWithRegionType:unauthRoleArn:authRoleArn:identityProvider: happens precisely the opposite.
In according with the documentation, using one or other of them, the authentication flow used is different.
I also read somewhere that is not possible to update or re-init the AWSServiceConfiguration
So, how can I switch from my BYOIProvider to Twitter and viceversa after a logout?
@FilippoGiove Hi Filippo, if you want to use both your BYOIProvider and Twitter together you should use initWithRegionType:identityPoolId:unauthRoleArn:authRoleArn:identityProviderManager: . In your implementation you should use identityProviderName and token from your BYOIProvider when logins is called and when they are using Twitter you should return logins for Twitter. Remember to call clearCredentials on the credentials provider when the user switches to force it to get new credentials on the next AWS call.
Thanks @behrooziAWS for your quickly reply.
In this way I have the following scenarios:
1 - I can login with Twitter, but If I logout (calling clearCredentials) and try to login with my BYOIProvider the following error occurs:
AWSiOSSDK v2.4.5 [Error] AWSIdentityProvider.m line:304 | __52-[AWSCognitoCredentialsProviderHelper getIdentityId]_block_invoke.255 | GetId failed. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=11 "(null)" UserInfo={__type=NotAuthorizedException, message=Invalid login token. Can't pass in a Cognito token.}]
2016-07-29 20:59:11.760 Dipty[3029:989075] AWSiOSSDK v2.4.5 [Error] AWSCredentialsProvider.m line:577 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke.352 | Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=11 "(null)" UserInfo={__type=NotAuthorizedException, message=Invalid login token. Can't pass in a Cognito token.}]
The logins map is set in this way:
Logins: {
"cognito-identity.amazonaws.com" = "BYOIProvider Token";
}
2 - Viceversa, if I log first in Twitter and the try to log with my BYOIProvider, I have the following error:
AWSiOSSDK v2.4.5 [Error] AWSCredentialsProvider.m line:527 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke | In refresh, but identityId is nil.
2016-07-29 21:04:07.662 Dipty[3035:990677] AWSiOSSDK v2.4.5 [Error] AWSCredentialsProvider.m line:577 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke.352 | Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoCredentialsProviderErrorDomain Code=1 "identityId shouldn't be nil" UserInfo={NSLocalizedDescription=identityId shouldn't be nil}]
The logins map is set in this way:
Logins: {
"api.twitter.com" = "XXXXX-YYYYYY--> Twitter token format";
}
@FilippoGiove Hi Filippo, thanks for that detailed output. It looks like I gave you incorrect advice on the constructor to use. You should use: initWithRegionType:identityProvider: and pass the BYOIProvider as the identityProvider. The [http://docs.aws.amazon.com/AWSiOSSDK/latest/Protocols/AWSCognitoCredentialsProviderHelper.html#//api/name/identityProviderManager identityProviderManager] your BYOIProvider returns should return the logins map for either your BYOIProvider or twitter depending on who you are logged in as. There is also some work to return the proper identityId when getIdentityId is called on your BYOIProvider. I'll dig into that part a bit further.
@FilippoGiove Take a look at this sample code for ideas on how to implement getIdentityId.
@sahilanguralla Are you sure you are correctly implementing getIdentityId to return the identityId returned by GetOpenIdTokenForDeveloperIdentity that was passed back from your backend after authentication with your backend?
@behrooziAWS Yes, I have implemented it as it is (@andresgarza implementation) and replaced by Backend API Call in refresh() method. I added print() statements to check function call sequence. Only token() was called which in turn called refresh(). Also If I call refresh() at app launch, it works fine. Sounds to be some issue with AWSTask. But still getIdentityId() was never called.
@sahilanguralla something I forgot to mention was that on app launch, I authenticate my user with my own server and after that was successful, I call credentialsProvider.credentials() to force the credentials to refresh.
I did notice that getIdentityId was not called, but my refresh method does get called, and that sets the identifyId directly self.identityId = json["identityId"].stringValue
@andresgarza Thanks for the reply. I am doing the same in my case. It started working after that.
Now, I am stuck with AWS Pre Signed URL Service. It generates a URL which says:
<Error>
<Code>PermanentRedirect</Code>
<Message>
The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint.
</Message>
</Error>
The region of the bucket is Global. So it should be accessible via any region.
@behrooziAWS If you could also look into this issue and suggest something, that would be great.
Thanks in advance.
@yosuke-matsuda @simoami @helloniklas
Totally agree to have the implementation included in the tutorial or in a blog post.
Took me a few days to get through...
Tutorials are misleading and for such a "common" function, if you have to write a "custom" class for it, you definitely want your user to understand why...
More importantly, login is probably the very first function any AWS user starts with. I can't describe how hard it was for me to start using AWS when the very first tutorial doesn't work. Who would expect that? :)
I agree totally with all the comments above, the documentation and sample code is really useless if one is using the latest SDKs. I have only recently started work with AWS Cognito and so naturally assumed going with the latest SDK would be the best choice, obviously not. I have a question regarding the unification of external login providers and User Pools. We wish to use Facebook, Twitter and Google as external login providers, as well as using an AWS Cognito User Pool for users who are not already registered with Facebook, Twitter or Google. I have managed to get Facebook, Twitter and Google logins working using a Custom Identity Provider, what I can NOT figure out is how to integrate User Pools into this equation. I have tried authenticating the user using the User Pool and that works fine, but when I add the identity token I receive from the User Pool into the logins keyed under "cognito-identity.amazonaws.com" I get an error
Invalid login token. Issuer doesn't match providerName
when trying to retrieve credentials. I am not sure how to proceed here, and I have read all the documentation from cover to cover to try and understand the process, but it's as clear as muddy water, the sample code I have won't even build when using the latest version of the SDK. Could someone please explain to me how to user a UserPool to authenticate a user in conjunction with external login providers. I do apologise if I have lost the plot here, but I find the documentation on AWS Cognito quite disgusting.
The steps I am following are
I created a custom identity provider as follows
class AWSCustomIdentityProvider: NSObject, AWSIdentityProviderManager
{
static let FacebookTokenKey = "graph.facebook.com"
static let GoogleTokenKey = "accounts.google.com"
static let TwitterTokenKey = "api.twitter.com"
static let CognitoTokenKey = "cognito-identity.amazonaws.com"
private var tokens : [NSString : NSString] = [:]
func addToken(key:String,value:String)
{
tokens[key] = value
}
@objc func logins() -> AWSTask
{
return AWSTask(result: tokens)
}
}
I init a credentials provider as follows using my custom identity provider, and with my identity pool details
_credentialsProvider = AWSCognitoCredentialsProvider(regionType:.USEast1,identityPoolId:"us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",identityProviderManager:_identityProvider)
let configuration = AWSServiceConfiguration(region:.USEast1, credentialsProvider:_credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
I add the token I receive from the external identity provider to the logins in my custom identity provider as necessary
I request credentials from the credentials provider when I need them
This all works for the external identity providers, and I assumed I could just use the identity token I received from the UserPool in the same way as the identity token I receive from the external identity providers, but keyed in the logins as "cognito-identity.amazonaws.com": user pool identity token. However this breaks with the "Invalid login token. Issuer doesn't match providerName" error when I attempt to retrieve credentials. Could someone please explain what I am doing wrong and how to use UserPools in this scenario.
Many thanks in advance
I have this problem too! With AWSMobileHubHelper framework I can use Facebook and google login,
But when integrate with user pool, I can login to my user pool independently, but I can't integrate it with federated id pool. I think there's a little steps I've missing.
I've post my code here:
Sample Code
Thanks for any help!
Unlike with external providers, User Pool does not require that you directly add a new login to the credentials provider. I use this code to assign the user pool credential to the credentials provider:
let serviceConfiguration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: nil)
let userPoolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: "YOUR_CLIENT_ID", clientSecret: "YOUR_CLIENT_SECRET", poolId: "YOUR_USER_POOL_ID")
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: userPoolConfiguration, forKey: "UserPool")
let pool = AWSCognitoIdentityUserPool(forKey: "UserPool")
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: "YOUR_IDENTITY_POOL_ID", identityProviderManager:pool)
Then get the user from pool.getUser(), call user.getsession(), and credentialsprovider.getIdentityId()
I am not sure that this is the best way, but it works for me.
@vincent-coetzee I think you can add the user pool login the way you are trying but the token key you are using is wrong. I believe it is this
cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>
@behrooziAWS Thank you for the code. Unfortunately for me it gives errors. Sign in works fine but if I close the app and return to it after a few hours, I get this: "GetCredentialsForIdentity keeps failing. Clearing identityId did not help.".
I've been grappling with Developer Authenticated Identities under v2.4 of the API for months now. I've found the code below seems to work almost all the time. I say "almost" because I had been using it without any problems for weeks, but one of my users received the same error (as above) the other day. Signing out and back in fixed it, but I'm worried that there is a subtle problem which will appear occasionally.
Here is my code. If anyone can explain why the error occurred, or what this code is missing, I'd be extremely grateful:
class DeveloperAuthenticatedIdentityProvider: AWSCognitoCredentialsProviderHelper {
var cachedLogin: [NSString : NSString]?
override func logins() -> AWSTask {
if (self.identityId != nil) && (self.cachedLogin != nil) {
return AWSTask(result: cachedLogin)
} else {
return self.getLogin()
}
}
func getLogin() -> AWSTask {
return self.token().continueWithBlock { (task:AWSTask!) -> AnyObject! in
if (task.result != nil) {
let jsonDictionary = task.result as! NSDictionary
self.identityId = jsonDictionary["identityId"] as? String
let login: [NSString : NSString] = ["cognito-identity.amazonaws.com" : jsonDictionary["token"] as! String]
self.cachedLogin = login
return AWSTask(result: login)
} else {
return AWSTask(result: nil)
}
}
}
override func token() -> AWSTask {
let awstask = AWSTaskCompletionSource()
let request = AFHTTPSessionManager()
var username = "username"
var password = "password"
let apiUrl = "https://" + username + ":" + password + "@something.com/api"
request.GET(apiUrl, parameters: nil, progress: nil,
success: {
(task: NSURLSessionDataTask, response: AnyObject?) -> Void in
awstask.setResult(response)
},
failure: {
(task: NSURLSessionDataTask?, error: NSError) -> Void in
awstask.setError(error)
}
)
return awstask.task
}
}
When the app launches I set the Default Service Configuration like this:
let identityProvider = DeveloperAuthenticatedIdentityProvider(regionType: cognitoRegion, identityPoolId: identityPoolId, useEnhancedFlow: true, identityProviderManager: nil)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: cognitoRegion, unauthRoleArn: nil, authRoleArn: nil, identityProvider: identityProvider)
let configuration = AWSServiceConfiguration(region: defaultServiceRegion, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = configuration
After authenticating my user with my server I simply call an AWS service (such as invoking a Lambda function). The API seems to take care of retrieving the identityId / token automatically (ie. no need to manually call credentialsProvider.credentials() or credentialsProvider.getIdentityId()).
I have followed this thread and I understand using the AWSIdentityProviderManager to provide logins mechanism to AWSCognitoCredentialsProvider instead of assigning the property directly works during initialization. However, how does one link an account to an existing identity with the 2.4 library changes? I would like to transition a guest user to a logged in user or attach a second login to an existing identity.
Even if I create a custom AWSIdentityProviderManager as described to allow login tokens to be altered, there is no way to refresh the credentials manager. If I resetKeychain on the credentials manager the anonymous identity is lost and a new identity is used based on the provider's login values.
@mystafer invalidateCachedTemporaryCredentials will just clear the credentials and leave the identity id. http://docs.aws.amazon.com/AWSiOSSDK/latest/Classes/AWSCognitoCredentialsProvider.html#//api/name/invalidateCachedTemporaryCredentials
@eleibu one thing that I notice is your getLogins will return successfully even on error with a nil logins map. Seems like return self.token().continueWithBlock should use continueWithSuccessBlock instead to pass the error on up otherwise. You can't go from an authenticated user to an unauthenticated user (nil logins) so if you've authenticated once successfully, this will cause access denied if your api ever returns an error.
@behrooziAWS Thank you very much, I'll give that a try.
By the way, like you and @sahilanguralla, I found that getIdentityId() was never called, which is why I didn't implement it. I note that I _have_ implemented logins() and you've implemented refresh(). Do you know which of these is required? I've checked and can confirm that logins() is being called.
Another difference between your code and mine is the "developer-provider-name". Seems strange to me that it would work either way.
@behrooziAWS Thank you for pointing me in the direction of invalidateCachedTemporaryCredentials.
I found that it invalidated the credentials locally but did not affect the identity in the pool when I checked the AWS dashboard. I could verify this because immediately after invalidating the credentials there was no communication with the server.
However, I did find that if I finished with a call to credentials() after invalidating that I could force the server to synchronize with the client and the login was indeed attached to the identity. Is the call to credentials() a requirement after invalidation to update the server or is there a better technique?
Is AWSSTSCredentialsProvider broken as well? We upgraded and it stopped working.
Edit:
Oh I see, this is actually our code.
Please update http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html since AWSAbstractCognitoIdentityProvider has been removed...
Anyone running into the issue where the AWSCognitoCredentialsProvider returns the same identityId value regardless of which user pool user being authenticated with? The identity pool seems to treat all of my user pool ids as unauthenticated identity requests (the login count for my pool is 0 in the federated identity dashboard), even though I have logged into this app on the same device with a few different accounts.
This is causing issues when I later try to use Cognito Sync. All users end up sharing the same dataset(s).
WTF? where is documentation? how to work with new AWS SDK (2.4.*)?
Demo not working with expired tokens. Got "Invalid login token. Not a Cognito token". Same backend.
[day after headache]
Current default implementation of [AWSCognitoCredentialsProviderHelper logins] :
if (self.identityProviderManager && self.useEnhancedFlow) {
self.cachedLogins = nil;
return [[self getIdentityId] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if(self.cachedLogins){
return [AWSTask taskWithResult:self.cachedLogins];
}
else {
return [self.identityProviderManager logins];
}
}];
}
return [[self token] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if (!task.result) {
return [AWSTask taskWithResult:nil];
}
NSString *token = task.result;
return [AWSTask taskWithResult:@{self.identityProviderName : token}];
}];
So, I'm passed...
useEnhancedFlow = NO
...to my "BYOIProvider", and it's works like in 2.3.6.
Update:
Working example: https://gist.github.com/xquezme/b62eb412ca25dd3a61ded1753ed91492
@mnolanjr98 Yes I have the same issue. Did you find the cause?
Nope, haven't figured it out. I think I read somewhere that you need to write a custom Identity Provider to use both Authenticated and Unauthenticated accounts, which seems silly seeing they are really promoting the User Pools. No real clear guidance on how to do this - it's really weird and frustrating that the SDK doesn't tie together all of the capabilities offered in the Mobile Hub (especially seeing it is not out of Beta).
I am using Authenticated provider (facebook), everything such as the AWSIdentityProviderManager however something weird is going on, every time I try to register the device it kept saying that "Unauthenticated access is not supported for this identity pool". But I know that I did not check the box for unauthenticated, it seems that cognito is treating facebook as one.
@LamourBt Are you using Xcode 8 in the simulator by any chance? If so, make sure you have enabled the Keychain Sharing entitlement capability under Targets->Capabilities->Keychain Sharing. We are tracking an apple radar issue where that must be enabled to get keychains and Facebook to work. With a simple implementation of the IdentityProviderManager below and Keychain Sharing on, i'm able to get it to work:
FacebookIdentityProviderManager.h
import <Foundation/Foundation.h>
#import <AWSCore/AWSCore.h>
#import <AWSCognito/AWSCognito.h>
@interface FacebookIdentityProviderManager : NSObject <AWSIdentityProviderManager>
@end
FacebookIdentityProviderManager.m
#import "FacebookIdentityProviderManager.h"
#import <FBSDKCoreKit/FBSDKCoreKit.h>
@implementation FacebookIdentityProviderManager
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
//Use Case 1. User is authenticated and you possess a valid token
//i.e. for Facebook
FBSDKAccessToken* fbToken = [FBSDKAccessToken currentAccessToken];
if(fbToken){
NSString *token = fbToken.tokenString;
return [AWSTask taskWithResult: @{ AWSIdentityProviderFacebook : token }];
}else {
return [AWSTask taskWithError:[NSError errorWithDomain:@"facebook"
code:-1
userInfo:@{@"error":@"No current Facebook access token"}]];
}
}
@end
@behrooziAWS yes I have done that before ... this is not the problem
2016-09-21 21:30:16.789601 LoginWithFaceBookIOS10[27752:6534364] AWSiOSSDK v2.4.9 [Debug] AWSURLSessionManager.m line:553 | -[AWSURLSessionManager printHTTPHeadersForResponse:] | Response headers:
{
Connection = "keep-alive";
"Content-Length" = 111;
"Content-Type" = "application/x-amz-json-1.1";
Date = "Thu, 22 Sep 2016 01:30:16 GMT";
"x-amzn-ErrorMessage" = "Unauthenticated access is not supported for this identity pool.";
"x-amzn-ErrorType" = "NotAuthorizedException:";
"x-amzn-RequestId" = "1d701ba6-8064-11e6-89f1-393f422a0785";
}
2016-09-21 21:30:16.790196 LoginWithFaceBookIOS10[27752:6534364] AWSiOSSDK v2.4.9 [Debug] AWSURLResponseSerialization.m line:63 | -[AWSJSONResponseSerializer responseObjectForResponse:originalRequest:currentRequest:data:error:] | Response body:
{"__type":"NotAuthorizedException","message":"Unauthenticated access is not supported for this identity pool."}
2016-09-21 21:30:16.793375 LoginWithFaceBookIOS10[27752:6534364] AWSiOSSDK v2.4.9 [Error] AWSIdentityProvider.m line:304 | __52-[AWSCognitoCredentialsProviderHelper getIdentityId]_block_invoke.255 | GetId failed. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=8 "(null)" UserInfo={__type=NotAuthorizedException, message=Unauthenticated access is not supported for this identity pool.}]
2016-09-21 21:30:16.793988 LoginWithFaceBookIOS10[27752:6534357] AWSiOSSDK v2.4.9 [Error] AWSCredentialsProvider.m line:605 | __46-[AWSCognitoCredentialsProvider getIdentityId]_block_invoke | In refresh, but identityId is nil.
2016-09-21 21:30:16.794154 LoginWithFaceBookIOS10[27752:6534357] AWSiOSSDK v2.4.9 [Error] AWSCredentialsProvider.m line:606 | __46-[AWSCognitoCredentialsProvider getIdentityId]_block_invoke | Result from getIdentityId is (null)
2016-09-21 21:30:16.795122 LoginWithFaceBookIOS10[27752:6534357] AWSiOSSDK v2.4.9 [Error] AWSCognitoService.m line:338 | __37-[AWSCognito registerDeviceInternal:]_block_invoke.262 | Unable to register device: Error Domain=com.amazon.cognito.AWSCognitoErrorDomain Code=-4000 "(null)"
Error while registering device: Optional("The operation couldn’t be completed. (com.amazon.cognito.AWSCognitoErrorDomain error -4000.)")
2016-09-21 21:30:30.949589 LoginWithFaceBookIOS10[27752:6534292] [MC] System group container for systemgroup.com.apple.configurationprofiles path is /private/var/containers/Shared/SystemGroup/systemgroup.com.apple.configurationprofiles
2016-09-21 21:30:30.958104 LoginWithFaceBookIOS10[27752:6534292] [MC] Reading from public effective user settings.
I should not get any of that issue since I am using authentication with facebook. I know that I did not check the box for unauthenticated (and if I do these credentials get saved as unauthenticated)
@LamourBt then it is likely you have not set up your credentials provider with a identityProviderManager that returns the logins map containing the Facebook token correctly. Turn on verbose logging to confirm, but if you see an empty logins map during the call to GetId, it isn't properly set up.
@behrooziAWS I am still having the same issue; I think the problem is with the defaultServiceConfiguration because in my appDelegate if I don't set the defaultServiceConfiguration the app would crash. I try to override that those once I have the facebook token from my singleton class but it did not override. So I upload the project on github, if you have a chance please take a look at my appDelegate and my singleton class
[(https://github.com/LamourBt/Facebook-Cognito/blob/master/LoginWithFaceBookIOS10/LoginWithFacebook%2BCognito.swift)]
@LamourBt I took a look at your code. Couple of things are going on here.
Some ideas for fixing it:
Keep in mind that once a user becomes authenticated, you must always setup the logins with a valid token from Facebook or whichever provider they authenticated with in order to get AWS credentials. You cannot transition from an authenticated user back to an unauthenticated user.
How can i set accessKey,secretkey by customising the provider class using these classes ? AWSCredentials, AWSCredentialsProvider
I am also trying to figure out how to unify external providers and User Pools, though I don't as of yet have any need for a custom login provider.
Is there any documentation out there or (better) sample code that shows how to do this? Getting even some chance of understanding architecture would be great, so we can start making plans on how we're going to implement all of this.
@mnolanjr98 Any solution to connecting your User Pool with a unique Identity Pool? I'm having the same issue.
Regarding connecting your User Pool with a unique Identity Pool, looks like after a successful getSession() call, you are supposed ti just call getIdentityId() or getIdentityId().continueWithBlock. It's not clear for a User Pool auth if the logins need to be provided. The current CredentialsProvider.logins call is deprecated. If I implement the custom AWSIdentityProviderManager, I'm not clear where to get the logins value. Is there a complete example anyone has come across that shows how to connect a User Pool with Identity Pool, so that each user has a unique Identity Pool?
@bpeck81 Re:User Pool/Identity Pool on Aug 6, I've been stuck on getting a unique Identity Pool for each new user for a while. My AppDelegate looks exactly like yours, but after signing up a new user, the logging them in with the pool.getUser(), call user.getsession(), and credentialsprovider.getIdentityId() sequence, each user is still using the same identity pool.
Any suggestions?
As a note, here's one way to put it together. This is using vincent-coetzee's class as well as some StackOverflow stuff. We're using amazon cognito for auth in our user pool.
The key was finally understanding the relationship between User Pools and the Federated Identities. The FI is really a bridge to amazon credentials; a FI doesn't do authentication, it basically provides authorization to AWS resources. You authenticate against a user pool because that's the part that understands authentication. The IdentityProvider allows the FI to map an amazon authenticated identity (those two IAM users) to a User Pool's authenticated identity.
Without further ado, here's the good stuff:
// from vincent-coetzee
// AWSCustomIdentityProvider.swift
// be sure to change your CognitoTokenKey
// YOUR_USER_POOL_ID should look like this -> us-west-5_bl0yh29v5
// your COGNITO_REGION should be like 'us-west-2'
import Foundation
import AWSCore
import AWSCognitoIdentityProvider
import AWSCognito
class AWSCustomIdentityProvider: NSObject, AWSIdentityProviderManager
{
static let FacebookTokenKey = "graph.facebook.com"
static let GoogleTokenKey = "accounts.google.com"
static let TwitterTokenKey = "api.twitter.com"
static let CognitoTokenKey = "cognito-idp.COGNITO_REGION.amazonaws.com/YOUR_USER_POOLID"
private var tokens : [NSString : NSString] = [:]
func addToken(key:String,value:String)
{
tokens[key] = value
}
@objc func logins() -> AWSTask
{
return AWSTask(result: tokens)
}
}
In your AppDelegate.swift. Note you don't have to do this here...and all you're doing is setting up your user pool.
let serviceConfiguration = AWSServiceConfiguration(region: Constants.COGNITO_REGIONTYPE, credentialsProvider: nil)
let configurationUserPool = AWSCognitoIdentityUserPoolConfiguration(clientId: Constants.COGNITO_USER_POOL_APP_CLIENT_ID, clientSecret: Constants.COGNITO_USER_POOL_APP_CLIENT_SECRET, poolId: Constants.COGNITO_USER_POOL_ID)
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: configurationUserPool, forKey: Constants.USER_POOL_KEY)
self.pool = AWSCognitoIdentityUserPool(forKey: Constants.USER_POOL_KEY)
To log in we ask the pool for a user with a specific username/email address/alias then getSession is what authenticates the user
//LOGIN
func login(email:String, password:String, callback:((error:String?)->Void)) {
let user = self.pool!.getUser(theUsernameOrAlias!)
user.getSession(theUsernameOrAlias!, password: password, validationData: nil).continueWithBlock({ task in
if let err = task.error { // some sort of error
print("LOGIN FAILED")
print("Domain: \(err.domain)! Code: \(err.code)")
//print(err.userInfo["message"] as! String)
if err.code == 23 { //Not sure here, exists doesn't mean CONFIRMED or UNCONFIRMED, maybe login?
callback(error:"UNCONFIRMED")
}
else {
callback(error:"ERROR")
}
}
else { //Successful login!
// this gets our token from the User Pool
let ret = task.result as! AWSCognitoIdentityUserSession
let myToken = ret.idToken?.tokenString;
print("Token: ", myToken);
// this sets up our custom provider. It allows cognito FI to map an identity to a user pool token.
// note that I believe the user pool token must be consistent.
let myProvider = AWSCustomIdentityProvider();
// let's add that token tot he provider
myProvider.addToken(AWSCustomIdentityProvider.CognitoTokenKey, value: myToken!)
// now we set that custom provider up as a reference for the Cognito FI
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USWest2, identityPoolId: Constants.COGNOTO_IDENTITY_POOL_ID, identityProviderManager:myProvider)
// add that FI credentials provider to the AWS config
let serviceConfiguration = AWSServiceConfiguration(region: Constants.COGNITO_REGIONTYPE, credentialsProvider: credentialsProvider)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfiguration;
// wipe cached creds
credentialsProvider.clearKeychain()
credentialsProvider.clearCredentials()
// hit it
credentialsProvider.getIdentityId().continueWithBlock { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error: ")
} else {
// the task result will contain the identity id
print("Success with id")
print(task.result)
print("After cp.logins call")
}
callback(error:nil)
return nil
}
}
return nil
})
}
This is with xcode 7 and swift 2.something or another. Hope this helps someone, this was sort of a nightmare to hunt down.
Here is some documentation for using DevAuth in Objective-C. It covers using DevAuth alone, DevAuth with unauthenticated and DevAuth with social providers. If you have feedback on the content or something isn't clear, please provide that feedback. This will be the basis for updating the documentation.
To use developer authenticated identities, implement your own identity provider class which extends AWSCognitoCredentialsProviderHelper.
@implementation DeveloperAuthenticatedIdentityProvider
/*
* Use the token method to communicate with your backend to get an
* identityId and token.
*/
- (AWSTask <NSString*>) token {
//Write code to call your backend:
//pass username/password to backend or some sort of refresh token to authenticate user, if successful,
//from backend call getOpenIdTokenForDeveloperIdentity with logins map containing "your.provider.name":"enduser.username"
//return the identity id and token to client
//You can use AWSTaskCompletionSource to do this asynchronously
// Set the identity id and return the token
self.identityId = response.identityId;
return [AWSTask taskWithResult:response.token];
}
@end
To use this identity provider, pass it into AWSCognitoCredentialsProvider as shown in the following example:
DeveloperAuthenticatedIdentityProvider * devAuth = [[DeveloperAuthenticatedIdentityProvider alloc] initWithRegionType:AWSRegionYOUR_IDENTITY_POOL_REGION
identityPoolId:@"YOUR_IDENTITY_POOL_ID"
useEnhancedFlow:YES
identityProviderManager:nil];
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc]
initWithRegionType:AWSRegionYOUR_IDENTITY_POOL_REGION
identityProvider:devAuth];
If you want to support both unauthenticated identities and developer authenticated identities, override the logins method in your AWSCognitoCredentialsProviderHelper implementation.
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
if(/*logic to determine if user is unauthenticated*/) {
return [AWSTask taskWithResult:nil];
}else{
return [super logins];
}
}
If you want to support developer authenticated identities and social providers, you must manage who the current provider is in your logins implementation of AWSCognitoCredentialsProviderHelper.
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
if(/*logic to determine if user is unauthenticated*/) {
return [AWSTask taskWithResult:nil];
}else if (/*logic to determine if user is Facebook*/){
return [AWSTask taskWithResult: @{ AWSIdentityProviderFacebook : [FBSDKAccessToken currentAccessToken] }];
}else {
return [super logins];
}
}
@behrooziAWS Thanks for this write up, the documentation is seriously lacking some info about this.
Was able to get this working using the logins method (the docs , again, are unclear about when to use token vs when to use logins, but it seems they dont work together)
refresh() protocol method anymore? You don't return those to the client at all, instead, upon completion of authentication with your backend, you call GetOpenIdTokenForDeveloperIdentity (from your backend) and return the token and identity id to the client. See details of what to do in the token method in the documentation above.
@behrooziAWS The docs you provided aren't clear on the correlation between logins and token . Also, if we're just providing token, we can't provide a custom providerName as a key like we would in the logins map. Should the logins method use the token from the token() method ?
A lot of confusion around this.
You don't need to override the logins method on ios, there already is an implementation for you if you extend AWSCognitoCredentialsProviderHelper. You only need to override it if you want to combine unauthenticated users or social login providers with your dev auth provider. The default implementation of logins sets the provider name to cognito-identity.amazonaws.com which is what it should be for dev auth. The place you need your custom provider name is when you call GetOpenIdTokenForDeveloperIdentity from your backend, not in ios.
And here is the swift documentation:
To use developer authenticated identities, implement your own identity provider class which extends AWSCognitoCredentialsProviderHelper.
import AWSCore
/*
* Use the token method to communicate with your backend to get an
* identityId and token.
*/
class DeveloperAuthenticatedIdentityProvider : AWSCognitoCredentialsProviderHelper {
override func token() -> AWSTask<NSString> {
//Write code to call your backend:
//pass username/password to backend or some sort of refresh token to authenticate user, if successful,
//from backend call getOpenIdTokenForDeveloperIdentity with logins map containing "your.provider.name":"enduser.username"
//return the identity id and token to client
//You can use AWSTaskCompletionSource to do this asynchronously
// Set the identity id and return the token
self.identityId = resultFromAbove.identityId
return AWSTask(result: resultFromAbove.token)
}
To use this identity provider, pass it into AWSCognitoCredentialsProvider as shown in the following example:
let devAuth = DeveloperAuthenticatedIdentityProvider(regionType: .YOUR_IDENTITY_POOL_REGION, identityPoolId: "YOUR_IDENTITY_POOL_ID", useEnhancedFlow: true, identityProviderManager:nil)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .YOUR_IDENTITY_POOL_REGION, identityProvider:devAuth)
let configuration = AWSServiceConfiguration(region: .YOUR_IDENTITY_POOL_REGION, credentialsProvider:credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
If you want to support both unauthenticated identities and developer authenticated identities, override the logins method in your AWSCognitoCredentialsProviderHelper implementation.
override func logins () -> AWSTask<NSDictionary> {
if(/*logic to determine if user is unauthenticated*/) {
return AWSTask(result:nil)
}else {
return super.logins()
}
}
If you want to support developer authenticated identities and social providers, you must manage who the current provider is in your logins implementation of AWSCognitoCredentialsProviderHelper.
override func logins () -> AWSTask<NSDictionary> {
if(/*logic to determine if user is unauthenticated*/) {
return AWSTask(result:nil)
}else if (/*logic to determine if user is Facebook*/){
if let token = AccessToken.current?.authenticationToken {
return AWSTask(result: [AWSIdentityProviderFacebook:token])
}
return AWSTask(error:NSError(domain: "Facebook Login", code: -1 , userInfo: ["Facebook" : "No current Facebook access token"]))
}else {
return super.logins()
}
}
@behrooziAWS Wouldn't it be better to add this to your official docs, they are very outdated, and downloading samples from mobile hub is even worst.
I've passed this content to the docs team. When they incorporate it, I will resolve this issue.
@behrooziAWS Can I please trouble you for a sample on how to use Digits with Cognito? I am encountering the same problem with trying to use Digits with the new SDK. I followed the existing examples for 2.36 and basically stuck on this step to set the logins :(
Please help.
@georgel likely you will need to implement an IdentityProviderManager that does something like this:
@implementation DigitsIdentityProviderManager
- (AWSTask<NSDictionary<NSString *, NSString *> *> *)logins {
AWSTaskCompletionSource<NSDictionary<NSString *, NSString *> *> * completion = [AWSTaskCompletionSource new];
[[Digits sharedInstance] authenticateWithCompletion:^
(DGTSession* session, NSError *error) {
if (!error) {
NSString value = [NSString stringWithFormat:@"%@;%@", session.authToken, session.authTokenSecret];
completion.result = @{@"www.digits.com", value};
} else {
completion.error = error;
}
}];
return completion.task;
}
@end
@behrooziAWS Thank you, David and all the helpful folks on this thread. I finally got it to at least login with Digit.
Can someone please give me a tip on how to go from un-auth user to an auth-ed user. When a user first open the app, they are assigned a un-auth identityId. After they login in, I am able to get the correct authed identityId. But when the app relaunches, the credentialProvider.identityId is still the un-auth id.
Here's what I piece together from all the helpful tips above. Please let me know if I am missing a step somewhere :)
An IdentityProviderManager:
class DigitsIdentityProviderManager:NSObject, AWSIdentityProviderManager {
public func logins() -> AWSTask<NSDictionary> {
let completion = AWSTaskCompletionSource<NSDictionary>()
if let configuration = DGTAuthenticationConfiguration(accountFields: .defaultOptionMask) {
configuration.appearance = DGTAppearance()
configuration.appearance.backgroundColor = UIColor.white
configuration.appearance.accentColor = UIColor.tintColor()
Digits.sharedInstance().authenticate(with: nil, configuration:configuration) {(session, error) in
if session != nil {
let value = session!.authToken + ";" + session!.authTokenSecret
completion.setResult(["www.digits.com" : value as NSString])
} else {
completion.setError(error!)
}
}
}
return completion.task
}
}
Invoked by
func handleDigitLogin() {
let digitsIdentityProviderManager = DigitsIdentityProviderManager()
let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.usEast1,
identityPoolId:Constants.Aws.CognitoPoolId,
identityProviderManager:digitsIdentityProviderManager)
let serviceConfiguration = AWSServiceConfiguration(region: .usEast1, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
let task = credentialsProvider.getIdentityId()
task.continue(successBlock: { (task:AWSTask) -> Any? in
if (task.error != nil ) {
print("\(task.error)")
} else {
print("Task result: \(task.result)")
}
return nil
})
}
If I may make a request. I am guessing it's challenging to keep docs up to date with each release. It would be nice if the SDK sample contained tests for the supported public providers and a sample custom provider. It would be much easier to use a test as the reference for me.
@georgel Once an identity becomes authenticated, you cannot get credentials against it unless you provide a valid token. The recommended approach is to instantiate your credentials provider in your AppDelegate because you can only set the defaultServiceConfiguration once. Whenever you need to access it you use AWSServiceManager.default().defaultServiceConfiguration.credentialsProvider If you are switching end users, you should use credentialsProvider.clearKeychain() this will blow away both your identity id and credentials. If you are switching between unauthenticated and authenticated you should use credentialsProvider.clearCredentials() because this will preserve your identity id. In other circumstances you shouldn't clear anything, i.e. if you relaunch the app and your credentials have expired the SDK will invoke your DigitsIdentityProviderManager.logins() to get a current token. To handle unauth and auth together, you should just have your DigitsIdentityProviderManager return a nil logins map when the user has never authenticated before otherwise call the Digits logic. If they sign out, you can start returning a nil logins map again. You should only call getIdentityId if you really need to, simply making calls to AWS services will get an identity id and credentials against that identity on demand if they aren't already present or have expired.
Thank you for your patience! The documentation has been updated for using developer authenticated identities with iOS SDK 2.4.x. http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html We will work to update the documentation for other identity providers, in the meantime there are a few examples of how to integrate with Facebook and Twitter in this thread.
If I leave out the auth roles from the AWSCognitoCredentialsProvider the request to upload to S3 times out:
Error Domain=NSURLErrorDomain Code=-1001 "The request timed out." UserInfo={NSUnderlyingError=0x171a45160 {Error Domain=kCFErrorDomainCFNetwork Code=-1001 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2102, _kCFStreamErrorDomainKey=4}}, NSErrorFailingURLStringKey=https://s3-eu-west-1.amazonaws.com/myserver-server/staging/avatar/1480977990.5395651.jpg, NSErrorFailingURLKey=https://s3-eu-west-1.amazonaws.com/myserver-server/staging/avatar/1480977990.5395651.jpg, _kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102, NSLocalizedDescription=The request timed out.}
If I include the roles:
Error Domain=com.amazonaws.AWSServiceErrorDomain Code=11 "(null)" UserInfo={Type=Sender, Message=Not authorized to perform sts:AssumeRoleWithWebIdentity, Code=AccessDenied, __text=(
"\n ",
"\n ",
"\n ",
"\n "
)}
Also in the developer authenticated identities Swift example, the identity provider is implemented using Objective-C which is of little help. It would be great to get the meat of that example in Swift.
I seemed to have solved my problem for my implementation of a Developer Authenticated only support. For those who were having similar troubles to me here is my setup. Open to getting this cleaned up if anyone has any suggestions. (Swift 3):
final class AmazonIdentityProvider: AWSCognitoCredentialsProviderHelper {
var cachedLogin: NSDictionary?
// Handles getting the login
override func logins() -> AWSTask<NSDictionary> {
guard let cachedLogin = cachedLogin else {
return getCredentials().continue({ credentialTask -> AWSTask<NSDictionary> in
guard let credential = credentialTask.result else {
return AWSTask(result: nil)
}
let login: NSDictionary = ["cognito-identity.amazonaws.com": credential.token]
self.cachedLogin = login
return AWSTask(result: login)
}) as! AWSTask<NSDictionary>
}
return AWSTask(result: cachedLogin)
}
// Handles getting a token from the server
override func token() -> AWSTask<NSString> {
return getCredentials().continue({ credentialTask -> AWSTask<NSString> in
guard let credential = credentialTask.result else {
return AWSTask(result: nil)
}
return AWSTask(result: credential.token as NSString)
}) as! AWSTask<NSString>
}
// Handles getting the identity id
override func getIdentityId() -> AWSTask<NSString> {
return getCredentials().continue({ credentialTask -> AWSTask<NSString> in
guard let credential = credentialTask.result else {
return AWSTask(result: nil)
}
return AWSTask(result: credential.identityId as NSString)
}) as! AWSTask<NSString>
}
// Gets credentials from server
func getCredentials() -> AWSTask<AmazonCognitoCredential> { // AmazonCognitoCredential is a class I created to store credentials from my server
let tokenRequest = AWSTaskCompletionSource<AmazonCognitoCredential>()
// Replace this with your code that calls your backend
Session.getAwsToken.makeRequest(failure: { error in
guard let error = error else { return }
tokenRequest.setError(error)
print("Could not fetch AWS credentials from server: \(error)")
}, completion: { credentials in
guard let credentials = credentials else { return }
tokenRequest.setResult(credentials)
})
return tokenRequest.task
}
}
/// Handles uploads to AWS S3
struct S3ImageUploader {
/// Singleton instance so that the credentials provider
/// can cache AWS credentials without having to hit
/// server constantly
static let main = S3ImageUploader()
private lazy var identityProvider: AmazonIdentityProvider = {
return AmazonIdentityProvider(regionType: .euWest1, identityPoolId: IdentityPoolId, useEnhancedFlow: true, identityProviderManager: nil)
}()
private lazy var credentialsProvider: AWSCognitoCredentialsProvider = {
return AWSCognitoCredentialsProvider(regionType: .euWest1, identityProvider: self.identityProvider)
}()
private lazy var serviceConfiguration: AWSServiceConfiguration = {
return AWSServiceConfiguration(region: .usWest2, credentialsProvider: self.credentialsProvider)
}()
/// Set the default service configuration
init() {
// Set configuration
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration
}
fun doUpload() {
// Your implementation here
}
}
final class AmazonCognitoCredential {
let token: String
let identityId: String
init(token: String, identityId: String) {
self.token = token
self.identityId = identityId
}
}
First me let me get this out of the way:
The AWS SDK is huge, VERY HARD to follow (if you practically didn't write it), IS NOT very well documented and has many bugs (many which I have uncovered).
To the iOS AWS SDK Team:
With that being said, after finally deciding to unpin my AWS SDK from v2.3.6 and dedicating some time to getting the latest SDK version working heres what I found after spending nearly ~8+ hrs dissecting the AWS SDK - alot of which was harder to debug in XCode due to all the callbacks implemented in the code. Enjoy 🎉!
To provide federated identities with cognito at the most basic level what has to happen is that your backend has to make a call to GetOpenIdToken to get a Cognito token for your Cognito Identity Pool. This token is returned to your iOS app and it is this token that will be used to authenticate with AWS.
1. Create a Custom Identity Provider that SubClasses AWSCognitoCredentialsProviderHelper
_Any references to [WinkAPI sharedManager].session imply checking if your app has a valid user session. Replace with your own calls to validate your app session_
//
// WinkIdentityProvider.h
// Wink
//
// Created by Adonis Peralta on 5/26/15.
// Copyright (c) 2015 TouchSix, Inc. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <AWSCore/AWSIdentityProvider.h>
@interface WinkIdentityProvider : AWSCognitoCredentialsProviderHelper
@end
//
// WinkIdentityProvider.m
// Wink
//
// Created by Adonis Peralta on 5/26/15.
// Copyright (c) 2015 TouchSix, Inc. All rights reserved.
//
#import <AWSCore/AWSCore.h>
#import "WinkAPI.h"
#import "WinkIdentityProvider.h"
#import "Session.h"
#import "AWSToken.h"
#import "Person.h"
@interface WinkIdentityProvider ()
@property (nonatomic, copy) NSString *awsToken;
@end
@implementation WinkIdentityProvider
- (AWSTask *)token
{
return [[AWSTask taskWithResult:nil] continueWithBlock:^id _Nullable(AWSTask * _Nonnull task) {
return [self refresh];
}];
}
// call to backend, called through entry point
- (AWSTask *)refresh
{
if (![WinkAPI sharedManager].session) return [AWSTask taskWithError:[NSError errorWithDomain:kWinkErrorDomain code:1 userInfo:@{NSLocalizedDescriptionKey:@"unable to refresh AWS token, no valid WinkAPI session."}]];
AWSTaskCompletionSource *task = [AWSTaskCompletionSource taskCompletionSource];
[[WinkAPI sharedManager] refreshAWSTokenWithCompletion:^(AWSToken *token, NSError *error) {
if (error) {
[task setError:error];
return;
}
self.identityId = token.identityId;
self.awsToken = token.uid;
[task setResult:self.awsToken];
}];
return task.task;
}
// tells aws in its api call if your user is authenticated or not - this correlates with the authRoleArn and unAuthRoleArn parameters of your identity pool
- (BOOL)isAuthenticated
{
if ([WinkAPI sharedManager].session) return YES;
return NO;
}
// override of the clear method of the identity provider so you can clean up your custom identity provider
- (void)clear
{
[super clear];
self.awsToken = nil;
self.identityId = nil;
}
@end
2. Initialize and setup your AWS Credentials in a class implementation of your choosing
// AWSHandlers.m
#import "WinkIdentityProvider.h"
#import "AWSCore.h"
#import "AWSS3.h"
@interface AWSHandlers ()
@property (nonatomic, strong) AWSCognitoCredentialsProvider *awsCredentialsProvider;
@property (nonatomic, strong) AWSS3TransferManager *s3TransferManager;
@end
@implementation AWSHandlers
- (void)setupAWS
{
// instantiate our custom identity provider
WinkIdentityProvider *winkProvider = [[WinkIdentityProvider alloc] initWithRegionType:AWSRegionUSEast1
identityPoolId:@"identity-pool-id-here"
useEnhancedFlow:YES
identityProviderManager:nil];
// instantiate the credentials provider with our customer identity provider
self.awsCredentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
identityProvider:winkProvider];
AWSServiceConfiguration *defaultAWSConfig = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:self.awsCredentialsProvider];
[[AWSServiceManager defaultServiceManager] setDefaultServiceConfiguration:defaultAWSConfig];
// setup a s3 transfer manager
self.s3TransferManager = [AWSS3TransferManager defaultS3TransferManager];
// change the verbosity level of the aws sdk if you choose
//[AWSLogger defaultLogger].logLevel = AWSLogLevelVerbose;
}
// call on app logout AND login! If not, cached credentials which may or may not be valid can be used during an app reinstall, or first loading of your app (not resuming from background)
- (void)invalidateAWSCredentialsAndHandlers
{
[self cancelAllCurrentS3Operations];
[self.awsCredentialsProvider clearKeychain];
self.awsCredentialsProvider = nil;
self.s3TransferManager = nil;
}
1. Initialize your custom identity provider via initWithRegionType:identityPoolId. Do not use the other methods that specify a identity provider manager as an identity provider manager is for when not using developer provided identities.
2. If the token you provided is invalid or EXPIRED the AWS SDK will attempt to refresh your token (via getIdentityId method) ONE more time. After that IF IT FAILS again the call that triggered the getting of AWS credentials (for example downloading an AWS image) will fail.
3. The Custom Identity Provider (BOOL)isAuthenticated method is needed to tell AWS in its api call if your user is authenticated or not. This correlates with the authRoleArn and unAuthRoleArn.
4. Override the Custom Identity Provider -(void)clear method to do any clean up of your custom identity provider variables if necessary.
After initializing and seting up the aws cognito credentials as described above in step #3 here are some quirks to know of about the AWS SDK:
**If you do not clear the provider keychain on login, the AWS SDK will attempt to use the keychain stored credentials instead of calling your backend for a valid token again. Failing to do so has very bad implications:
Update: The above post has been updated to factor a misunderstanding about the IdentityProviderManager class. Initially I believed it was part of the developer identity provider flow but it is not. This simplifies many of the findings I had found previously in regards to the use of enhancedFlows vs non-enhanced throughout the authentication process.
Hello,
To login with Developer Authenticated Identities in SDK AWS v2.4.16 I have a ILDeveloperAuthenticatedIdentityProvider (:AWSCognitoCredentialsProviderHelper
) where has implemented “(AWSTask
Why could this be happening?
Hi @ivanfervar,
Did you read my post above above. Its a bit long and detailed but will definitely explain what's happening behind the scenes and should help you out.
On Jan 11, 2017, at 7:33 AM, ivanfervar notifications@github.com wrote:
Hello,
To login with Developer Authenticated Identities in SDK AWS v2.4.16 I have a ILDeveloperAuthenticatedIdentityProvider (:AWSCognitoCredentialsProviderHelper
) where has implemented “(AWSTask*)token” among others like AWS Doc explain, but this “token” function never called.
Why could this be happening?—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
@donileo i don't suggest you use the approach that you suggested
- (AWSTask *)logins
{
if (![WinkAPI sharedManager].session) {
return [AWSTask taskWithError:[NSError errorWithDomain:kWinkErrorDomain code:1 userInfo:@{NSLocalizedDescriptionKey:@"Unable to get a proper logins map, no valid wink session found"}]];
}
// NOTE: here we are using the AWSIdentityProviderAmazonCognitoIdentity which is @"".
// NEVER send back the identity provider name as the key here. Thats only ever meant to be used in the
// generation of the cognito token in the backend.
return [AWSTask taskWithResult:@{AWSIdentityProviderAmazonCognitoIdentity: [WinkAPI sharedManager].session.awsToken.uid}];
}
is more of an approach used in older release (2.3.6 and before). It doesnt apply and will not work well if you use a 2.4.0 or higher version of sdk. From 2.4.0 the logins method should always return a current token. If you cache the token then it will eventually expire and the sdk will only call logins when it is refreshing the credentials.
I suggest you implement it the way @behrooziAWS suggested a couple of comments before yours by just overriding the method - (AWSTask <NSString*>) token which will make call to your backend and fetch the current token. You can read more about it here
@karthiksaligrama the approach I put above is based on analyzing how the SDK currently works in 2.4.0. I am overriding the logins method in the IdentityProviderManager not the IdentityProvider. In the SDK, when using the enhanced flow (non-api) the SDK will call the IdentityProviderManager's login method after it refreshes the token via the (getIdentityId call of the identityProvider - making a backend call) from the IdentityProvider.
As far as what I'm noticing thats what I'm supposed to do. Get a valid token, store it in [WinkAPI sharedManager].session. When the token expires (an expiration error condition happens in the SDK) my Identity provider will refresh the token and my [WinkAPI sharedManager].session will have the new token. Then the logins method in my provider manager will return a valid token. I have not been seeing any issues whatsoever with my method.
Also, if you override the - (AWSTask
Please see the lines starting with 262 in AWSIdentityProvider.m:
if (self.identityProviderManager && self.useEnhancedFlow) {
self.cachedLogins = nil;
return [[self getIdentityId] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if(self.cachedLogins){
return [AWSTask taskWithResult:self.cachedLogins];
}
else {
return [self.identityProviderManager logins];
}
}];
}
return [[self token] continueWithSuccessBlock:^id _Nullable(AWSTask<NSString *> * _Nonnull task) {
if (!task.result) {
return [AWSTask taskWithResult:nil];
}
NSString *token = task.result;
return [AWSTask taskWithResult:@{self.identityProviderName : token}];
}];
@donileo i get that. You really shouldnt be setting the identityProviderManager at all. You should just extend the AWSCognitoCredentialsProviderHelper and override the method - (AWSTask *)token. If you override the IdentityProviderManager and return a cached token then you will run into problems. The token that you get from Cognito GetOpentIdTokenForDeveloperIdentity is only valid for 15 mins. If you are caching anything to not require the user to provide the user name and password again you should be caching a refresh token against your backend and use that refresh token and get an new OpenIdToken by calling GetOpentIdTokenForDeveloperIdentity each time.
When ever you are using developer authenticated identities you need to use the constructor.
- (instancetype)initWithRegionType:(AWSRegionType)regionType
identityProvider:(id<AWSCognitoCredentialsProviderHelper>)identityProvider;
@karthiksaligrama what is the IdentityProviderManager for then? Yes, if you were to use that method above only and not provide an identityProviderManager then yea you should implement the tokens method in your identityProvider. As far as my code, I did implement an IdentityProviderManager and of course when you do you should implement the logins method in your provider manager. Can you please explain whats wrong with my method. I believe both are fine. Seems like you are implying the token returned by the IdentityProvider tokens method is not the same as the one returned in the logins method of the IdentityProviderManager. As far as my backend, yes my backend does call GetOpentIdTokenForDeveloperIdentity and provide my client code with a new AWS token essentially refreshing my token, it does it successfully when it expires. What triggers the call? The AWS SDK detects when the token has expired, and places my backend call to refresh/provide a new token by calling GetOpentIdTokenForDeveloperIdentity. Don't see anything wrong there.
The IdentityProviderManager is for when you are not using developer provided identities. If you are using developer provider identities at all, then the logins method on AWSCognitoCredentialsProviderHelper serves the role as the IdentityProviderManager.
@behrooziAWS got it :). As far as my code it still works, just that my refresh token call should also be in the token method and theres no need for my Custom IdentityProviderManager. Thanks! I'll update the post I made up above.
@behrooziAWS Where is this differentiation on AWSIdentityProviderManager and AWSCognitoCredentialsProviderHelper noted in the documentation?
Are there any code samples that demonstrate how to use either (or both) of them as intended?
@kdbertel @behrooziAWS also if the AWSIdentityProviderManager is not part of the developer provider identities flow at at all why is it being asked for on the initialization to the AWSCognitoCredentialsProviderHelper? Please see method:
initWithRegionType:identityPoolId:useEnhancedFlow:identityProviderManager:
@donileo It is not required. If you have an identity provider manager that implements all of your other non developer authenticated providers, you can call it in the logins method in your AWSCognitoCredentialsProviderHelper.
To avoid further confusion, no one from AWS will continue to respond to this thread. There are several implementations in the comments that fail for certain edge cases, don't properly refresh tokens, are more complicated than necessary or are for unrelated issues. Too many people are arriving at this thread, not following the official guidance and ending up with issues. The official guidance is here: http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html Please open a new issue if you have issues with the official guidance.
This was either hard to find or did not exist until recently. Thank your for putting this up here.
On Jan 12, 2017, at 1:49 PM, David Behroozi notifications@github.com wrote:
To avoid further confusion, no one from AWS will continue to respond to this thread. There are several implementations in the comments that fail for certain edge cases, don't properly refresh tokens, are more complicated than necessary or are for unrelated issues. Too many people are arriving at this thread, not following the official guidance and ending up with issues. The official guidance is here: http://docs.aws.amazon.com/cognito/latest/developerguide/developer-authenticated-identities.html Please open a new issue if you have issues with the official guidance.
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub, or mute the thread.
@behrooziAWS I'm not stating that it "is" required. Simply, if its not, why surface it up so high up to designated initializer of the AWSCognigoCredentialsProviderHelper. The fact that its there makes it seem that it is. The official docs? Let us all hope they are good now :/.
@behrooziAWS sorry! but your comment is not serious:
Firstly, I know this link because in a issue ticket, a AWS support guy told my team we should try with @donileo information. After your comment, that response mysteriously disappeared.
Secondly, Open new issue? we have opened dozens tickets about cognito and IOS, We always receive the same reponse, _"are you use clearKeychain? are you instantiating 2 sync clients? ahh OK!!!! I have passed the information to the Cognito Team"_. I opened a ticket in October and the last weekend (3 months) the support team guy ask us again... _"are you use clearKeychain?"_ OMG!!!!!
Do you understand why people are arriving at this thread?
@drsromero exactly. Theres a reason why this thread exists and is the amount of comments you see; horrible documentation from AWS. My original post detailing how to properly implement the auth flow was the best that I could implement based on what I knew, and what I discovered debugging the SDK. I also figured I'd try and help others given how crazy this issue had become. After they provided new info (that the manager isn't for dev authenticated flows) it changed a lot of the information I had posted and the post is now updated to match this simpler flow. But if the manager is used then all the info in my original post all applies again. Again its not a coincidence this thread is what it is.
Btw, looking at the docs @behrooziAWS posted it seems that the IdentityProviderManager is to be used during dev federated identities when you want to provide auth for the standard providers as well. He seemed to imply the manager is not part of the flow at all, which definitely contradicts the link he posted. Once you add the manager, like in this case, I'd say my original post applies again.
God, after spending HOURS trying to solve "Invalid token" error I've noticed that using wrong initializer for AWSCognitoCredentialsProvider.
It should be initWithRegionType:identityProvider: (not _initWithRegionType:identityPoolId:identityProviderManager:_ !!!).
Also, there is no any difference between [logins] and [token] overriding for developer identities.
[token] is just helper to automatically build login map like _@{ self.identityProviderName : token }_
And, yes, it's better to pass self.identityProviderName to get logins key for developer identities since it returns _"cognito-identity.amazonaws.com"_
Hope it helps someone in trouble...
Most helpful comment
breaking changes like this should not be a point update 👎. Dealing with this same issue myself. What makes it worse is documentation of this breaking change shouldve gone up with the 2.4.0 release :(.