Elliot Elliot - 4 months ago 423
iOS Question

AWS Cognito User Pools in iOS (Swift) app

I'm trying to implement the new AWS Cognito User Pools in my iOS (Swift) app, but I'm struggling to get the sign in process to work. I am essentially trying to follow the example available here.

This is what I have so far:

AppDelegate:

class AppDelegate: UIResponder, UIApplicationDelegate, AWSCognitoIdentityInteractiveAuthenticationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let serviceConfiguration = AWSServiceConfiguration(region: AWSRegionType.USEast1, credentialsProvider: nil)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfiguration
let configurationUserPool = AWSCognitoIdentityUserPoolConfiguration(
clientId: "###",
clientSecret: "#########",
poolId: "###")
AWSCognitoIdentityUserPool.registerCognitoIdentityUserPoolWithConfiguration(serviceConfiguration, userPoolConfiguration: configurationUserPool, forKey: "UserPool")
self.userPool = AWSCognitoIdentityUserPool(forKey: "UserPool")

self.userPool!.delegate = self

return true
}

func startPasswordAuthentication() -> AWSCognitoIdentityPasswordAuthentication {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let logInNavigationController = mainStoryboard.instantiateViewControllerWithIdentifier("LogInNavigationController") as! UINavigationController

dispatch_async(dispatch_get_main_queue(), {
self.window?.rootViewController = logInNavigationController
})

let logInViewController = mainStoryboard.instantiateViewControllerWithIdentifier("LogInViewController") as! LogInViewController
return logInViewController
}
}


LogInViewController:

class LogInViewController: UIViewController, AWSCognitoIdentityPasswordAuthentication {
var usernameText : String?
var passwordAuthenticationCompletion = AWSTaskCompletionSource()

func getPasswordAuthenticationDetails(authenticationInput: AWSCognitoIdentityPasswordAuthenticationInput, passwordAuthenticationCompletionSource: AWSTaskCompletionSource) {
self.passwordAuthenticationCompletion = passwordAuthenticationCompletionSource

dispatch_async(dispatch_get_main_queue(), {
if self.usernameText == nil {
self.usernameText = authenticationInput.lastKnownUsername
}
})
}

func didCompletePasswordAuthenticationStepWithError(error: NSError) {
dispatch_async(dispatch_get_main_queue(), {
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let mainNavigationController = mainStoryboard.instantiateViewControllerWithIdentifier("MainNavigationController") as! UINavigationController
(UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController = mainNavigationController
})
}

func logInButtonPressed() {
self.passwordAuthenticationCompletion.setResult(AWSCognitoIdentityPasswordAuthenticationDetails(username: emailTextField.text, password: passwordTextField.text))
}
}


Nothing seems to happen when I hit the log in button, although if I hit it again I get an NSInternalInconsistencyException (which I believe is because the AWSTask result has already been set).

Any help with this would be appreciated. I am using the AWS SDK for iOS version 2.4.1.

UPDATE:

Not a solution to my original problem, but I've been able to get User Pools working by using the explicit sign in method rather than the delegate method (see this page for details). Here is the code from my SignInViewController:

class SignInViewController: UIViewController {
@IBAction func signInButtonTouched(sender: UIButton) {
if (emailTextField.text != nil) && (passwordTextField.text != nil) {
let user = (UIApplication.sharedApplication().delegate as! AppDelegate).userPool!.getUser(emailTextField.text!)
user.getSession(emailTextField.text!, password: passwordTextField.text!, validationData: nil, scopes: nil).continueWithExecutor(AWSExecutor.mainThreadExecutor(), withBlock: {
(task:AWSTask!) -> AnyObject! in

if task.error == nil {
// user is logged in - show logged in UI
} else {
// error
}

return nil
})
} else {
// email or password not set
}
}
}


Then, to consume an AWS service (which in my case is located in a different region to Cognito) I have created a new Credentials Provider using the User Pool:

let credentialsProvider = AWSCognitoCredentialsProvider(regionType: .USEast1, identityPoolId: "###", identityProviderManager: (UIApplication.sharedApplication().delegate as! AppDelegate).userPool!)
let serviceConfiguration = AWSServiceConfiguration(region: .APNortheast1, credentialsProvider: credentialsProvider)
AWSLambdaInvoker.registerLambdaInvokerWithConfiguration(serviceConfiguration, forKey: "Lambda")
let lambdaInvoker = AWSLambdaInvoker(forKey: "Lambda")


One additional issue is that I was seeing this error each time I launched the app: "Could not find valid 'AWSDefaultRegionType', 'AWSCognitoRegionType', and 'AWSCognitoIdentityPoolId' values in info.plist.". This seems to be related to Fabric, which I am using to track crashes. I've solved this by changing this line in the AppDelegate:

Fabric.with([AWSCognito.self, Crashlytics.self])


to this:

Fabric.with([Crashlytics.self])


I hope this helps someone else.

Answer

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.

Comments