iOS

AWS Cognito User Pools with mobile sdk for iOS using custom challenge

I was integrating AWS Cognito user pool to an iOS application. The sign in feature is using custom challenge for authentication, but there is a lack of documentation about how to use the iOS sdk. After many trial and errors, I have finally able to sign in success, so I’m going to document the steps as shown below:

Step 1: Create a CognitoUserPool

In AppDelegate, after didFinishLaunchingWithOptions, the user pool is initialised.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  // setup 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 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: After sign in success, you can get the username and user attribute:

self.user?.getDetails().continueOnSuccessWith { (task) -> AnyObject? in
DispatchQueue.main.async(execute: {
self.response = task.result
// With user details
print(response)
})
return nil
}

Please let me know if you have any questions. I hope AWS could update the documentations and provide sample example code to save us time to understand the sdk through trial and error.

Fix iOS 10 permission crashes error

I am developing an app with request access to user’s microphone. It was working fine with iOS 9, but since upgrade to iOS 10, it terminates with error message shown in the terminal:

This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app’s Info.plist must contain an NSMicrophoneUsageDescription key with a string value explaining to the user how the app uses this data.
(lldb)

To solve the problem, edit the Info.plist file as source code and add the following:

<key>NSMicrophoneUsageDescription</key>    
<string>Some microphone description whatever you want</string>

For your reference, in case you need access to user’s camera, use the follow:

<key>NSCameraUsageDescription</key>    
<string>Some kind of camera request description</string>

Or request for contacts access for your reference.

<key>NSContactsUsageDescription</key>    
<string>This app request access for your contacts</string>

Happy coding!

Connection between .h and .m files in Objective-C

When first open an objective-C project in X-code, the .h and .m files looks confusing. It is important to understand the simple connections and the hidden codes behind the scene.

They are used to separate between the public and private parts of the class. The .h file is a header file for public declarations of your class like an API, while the .m file is the private implementation.

When you need to call a function at the other files, just need to import the .h files for referencing. For example,

#import <Foundation/Foundation.h>

In the .h file, we can declare public @property of the class, which can be called from outside:

@property (strong, nonatomic) NSString *something;

Here the @property is a pointer to an object whose class is NSString. All objects live in the heap, thus we need the *. As a side note, the strong means “keep the object points to in memory until I set this property to nil”. Nonatomic means “access to this property is not thread-safe”, otherwise the compiler will generate locking code.

In the .m file, the “getter” and “setter” methods of this property is automatically generates for you behind the scene in order to make the @property’s instance accessible:

@synthsize something = _somthing; 
- (NSString *) something
{
return _something;
}
- (void)setSomething:(NSString *)something
{
_something = something;
}

Notice that by default the backing variable’s name is the same as the property’s name with an underscore in front. You don’t need to write the above code, unless you want to override the method and do something differently.

When you create a new method, you need to put the declaration in .h file:

- (int)newMethod:(ArgType *)arg;

And then write the actual details in your .m file.

- (int)newMethod:(ArgType *)arg 
{
int num = 0;
# something in the method...
return num;
}

Also, for private declarations, you can put inside the .m files like this:

@interface Something() 
#private declarations....
@end

Finally, when you are reading other codes for the first time, just need to look at the .h files to give you an overview of the projects, unless you need to deep dive in the details, then look at the .m files.

Understanding the above fundamental concept and the rest of the code would starts to make sense 😀


Originally published at victorleungtw.com on November 8, 2015.