AWS Cognito User Pools with Mobile SDK for iOS Using Custom Challenge


I recently integrated an AWS Cognito User Pool into an iOS application. The sign-in feature utilizes a custom challenge for authentication. However, there is limited documentation on how to use the iOS SDK for this purpose. After several trials and errors, I finally succeeded in signing in. Below are the steps to accomplish this:

Step 1: Create a CognitoUserPool

In the AppDelegate, after didFinishLaunchingWithOptions, the user pool is initialized as follows:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  // Set up service configuration
  let serviceConfiguration = AWSServiceConfiguration(region: CognitoIdentityUserPoolRegion, credentialsProvider: nil)

  // Create pool configuration
  let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: CognitoIdentityUserPoolAppClientId, clientSecret: nil, poolId: CognitoIdentityUserPoolId)

  // Initialize user pool client
  AWSCognitoIdentityUserPool.register(with: serviceConfiguration, userPoolConfiguration: poolConfiguration, forKey: AWSCognitoUserPoolsSignInProviderKey)

  // Fetch the user pool client we initialized in the above step
  let pool = AWSCognitoIdentityUserPool(forKey: AWSCognitoUserPoolsSignInProviderKey)

  self.storyboard = UIStoryboard(name: "Main", bundle: nil)

  pool.delegate = self

  return true
}

Step 2: Implement the Protocol Delegate

extension AppDelegate: AWSCognitoIdentityCustomAuthentication {

  func didCompleteStepWithError(_ error: Error?) {

  }

  func getCustomChallengeDetails(_ authenticationInput: AWSCognitoIdentityCustomAuthenticationInput, customAuthCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityCustomChallengeDetails>) {

  }

  func startCustomAuthentication() -> AWSCognitoIdentityCustomAuthentication {
    if self.navigationController == nil {
      self.navigationController = self.storyboard?.instantiateViewController(withIdentifier: "signinController") as? UINavigationController
    }

    if self.signInViewController == nil {
      self.signInViewController = self.navigationController?.viewControllers[0] as? SignInViewController
    }

    DispatchQueue.main.async {
      self.navigationController!.popToRootViewController(animated: true)

      if !self.navigationController!.isViewLoaded || self.navigationController!.view.window == nil {
        self.window?.rootViewController?.present(self.navigationController!, animated: true, completion: nil)
      }
    }

    return self.signInViewController!
  }
}

Step 3: Handle the Custom Challenge Inside the Sign-In View Controller

extension SignInViewController: AWSCognitoIdentityCustomAuthentication {

  func getCustomChallengeDetails(_ authenticationInput: AWSCognitoIdentityCustomAuthenticationInput, customAuthCompletionSource: AWSTaskCompletionSource<AWSCognitoIdentityCustomChallengeDetails>) {

    let authDetails = AWSCognitoIdentityCustomChallengeDetails(challengeResponses: ["USERNAME": "YourUserName", "ANSWER": "123456"])
    customAuthCompletionSource.set(result: authDetails)
  }

  public func didCompleteStepWithError(_ error: Error?) {
    DispatchQueue.main.async {
      if let error = error as? NSError {
        print("error")
      } else {
        print("success")
        self.dismiss(animated: true, completion: nil)
      }
    }
  }
}

Step 4: Access User Attributes After Successful Sign-In

self.user?.getDetails().continueOnSuccessWith { (task) -> AnyObject? in
  DispatchQueue.main.async(execute: {
    self.response = task.result

    // Display user details
    print(response)
  })

  return nil
}

If you have any questions, please feel free to ask. I hope AWS updates the documentation and provides sample code to make it easier to understand the SDK without having to resort to trial and error.