AWS Cognito User Pools in iOS (Swift) app

Just adding my 2 cents for people who work with Objective-c and the sample app CognitoYourUserPoolsSample provided by Amazon. @Bruce0 already covered everything with his swift solution. But if you're running into this problem where getPasswordAuthenticationDetails doesn't get called when you hit sign-in is because you're not calling [self.user getDetails] at all. Indeed - getDetails triggers getPasswordAuthenticationDetails. If you look closer in the AWS sample app, they call it right when they launch the app in viewDidLoad of the UserDetailTableViewController, which is the first controller that gets loaded. If the user is not signed-in then getDetails response somehow triggers the SignInViewController. This is what I will explain below. It's like a "myHomeViewController" kind of, where you want to display user related info. Otherwise, you want to display the login/sign-up screen by default.

  • As a general rule of thumb, connect and init the Cognito User Pool in your AppDelegate (didFinishLaunchingWithOptions) exactly like they did in the sample app. Make sure to add the AWSCognitoIdentityInteractiveAuthenticationDelegate and implement startPasswordAuthentication where you'll bring up your sign-in ViewController. Let the AppDelegate take care of the WHAT_TO_DO_IF_USER_NOT_SIGNED_IN (e.g. bring the SignInViewController on top) then focus on the WHEN_DOES_THE_USER_NEEDS_TO_SIGNIN somewhere in your app.

  • When you need user specific data then tell the App that it's time to check if the user is signed-in (self.user getDetails). Again, if user isn't signed-in then the AppDelegate knows what to do. It over-rules the app and displays the sign-in View on top of everything. So, it could be right at the beginning (e.g. Facebook, Twitter, etc.) or somewhere else (e.g. Ebay, etc.). Simply call [self.user getDetails] at the end of viewDidLoad. This will prevent the current ViewController to show prior to the authentication step (sign-in/sign-up) OR simply load the current ViewController if the user is already signed-in.

When using AWS User Pool feature in your app, follow those steps:

  1. Find out where you need user specific data in any of YourViewControllers
  2. in the related ViewDidLoad, call [self.user getDetails]
  3. if User is signed-in already then display user specific data using the completionHandler of self.user getDetails.
  4. if not then startPasswordAuthentication gets automatically called in the AppDelegate, which brings up the sign-in ViewController before displaying YourViewController since you need user specific data
  5. User sign-in or sign-up
  6. dissmiss sign-in/sign-up ViewController and there you are back into YourViewController which can now be loaded with some user specific data.

The AWS sample App isn't straight forward but it is really simple.


Update 6: (and really final this time )

It is worth mentioning that (finally) AWS has made the AWS Mobile Hub build a very nice demo app that INCLUDES User Pools as a SignInProvider (With Google and Facebook too). The architecture is (in my opinion) excellent (they have separated Identity Management and getting credentials from Authentication) Check it out

Update 5: (and final)

There is a fairly complete example implementation, and some documentation of how it works in this other answer.

iOS - AWS MobileHub sign in with developer authenticated provider

Update 4:

If you want to get access to AWS services, there are more steps needed

It turns out that this does not get you authenticated with Cognito Federated Identities (the "logins" count on the identity browser remains at 0). To fix this you need to establish a credentialsProvider and do "credentialsProvider.getIdentityId". After that logins will show positive, and you can get services from AWS based upon your authenticated role.

If you are trying to do both Authenticated and UnAuthenticated access for your mobile app, then you need to create an AWSAnonymousCredentialsProvider (in a separate service configuration). Then you self.credentialsProvider?.invalidateCachedTemporaryCredentials() and self.credentialsProvider?.clearCredentials() when logging out and do the getidentityid again with the anonymous service configuration and you will get an anonymous id. (Note: I found it seemed like if you clearkeychain on the credentialsProvider it starts with a new id each time a user logs out, which could burn up your free 50,000 ids pretty quick. )

Update 3:

Uploaded a github sample app for AWS User Pools for IOS in Swift.

https://github.com/BruceBuckland/signin

Update 2:

I finally got AWS User Pools to work correctly in Swift

My problem was that each time the authentication start happened it was caused by an authentication failure in a different viewcontroller (my error). I ended up with a bunch of them running waiting for completion returns which never came and the API was "silent" (showed no errors). The API does not notice that it is being initiated multiple times ( by a different viewController each time) so it silently lets log in over and over. There is not enough of your code in the original post to see if you are having that same issue.

You have to be careful, the AWS sample code (in Objective-C) has two navigation controllers, and the code re-uses them. I don't like the way the sample app flashes the logged in view controller before the authentication delegate gets going and I was trying to improve that in the swift version and that caused my problem.

AWS User Pools API is set up to work with a storyboard or app structure that works like this:

1) Your app ASSUMES it is logged in, and then triggers the delegate which triggers authentication and the login screens if it is not.

2) In original logged in view controller pool.currentUser() is NOT enough to get the authentication going, API will only trigger the delegate when you do more (in my case user.getDetails()).

3) The authentication is completed through the didCompletePasswordAuthenticationStepWithError. This delegate method is called if you get an authentication (or other) error AND if you SUCCESSFULLY authenticate. In the case of successful authentication the NSError is nil, so it should be declared as NSError? in the delegate (this causes a warning). The API is beta, they will probably fix this.

4) One other little “gotcha”, it may be obvious to you, it caught me, when you define your User Pool in the console you specify allowed apps, and each of these apps HAS DIFFERENT STRINGS for Client ID strings. ( I was just plugging the same thing into the example) which works badly (but does not report errors). The API needs some work in the reporting department. It is very Verbose when it is working, but says nothing if you pass it the wrong Client Strings. Also it seems to say nothing if you (like I did) call the API from different viewcontrollers. It was just taking each new authentication request from a different viewcontroller and saying nothing.

Anyway, it works now. I hope this helps resolve your issue.

Update:

I finally got getPasswordAuthenticationDetails to execute.

It turns out it does not get executed until user.getDetails for the current user (even if there is no current user).

So

let user = appDelegate.pool!.currentUser() let details = user!.getDetails()

will result in the getPasswordAuthenticationDetails callback getting executed on the second line.

It seems the AWS UserPool concept is that we write an app that assumes we have a logged in user. We get details from that user (for instance in the initial view controller) and the delegate gets kicked off if we don't have a user.

The AWS documentation for User Pools on IOS is missing some important concept pages. Those pages ARE included in the (otherwise parallel) Android documentation. I admit that I am still struggling (days now) with getting User Pools to work in swift, but reading the "Main Classes" and "Key Concepts" Parts of the Android documentation clarified a lot for me. I can't see why it was omitted from the IOS doc.