The type of error thrown by the Amplify Authentication library is an AuthError type, which contains a more helpful embedded AWSCognitoAuthError type. The AWS documentation is confusing and misleading because AWS provides two SDKs at this time: the AWS Mobile SDK and the newer, dumber Amplify Libraries.
It looks like you are using the Amplify Libraries. If you break your code in the Xcode debugger on the .failure case, the debugger variable display shows error as Amplify.AuthError which requires a PhD in AWS to make sense of. But for purposes of illumination, if you slug this in under the .failure case
case .failure(let error):
if let actualError = error.underlyingError as NSError? {
print("Cast to nserror:", actualError)
}
you will get Cast to nserror: AWSCognitoAuthPlugin.AWSCognitoAuthError.xxx where the xxx might be usernameExists or some other useful error. It turns out AWSCognitoAuthError is a straightforward enum which you can peruse by typing AWSCognitoAuthError at some random spot in your source, then right-clicking and choosing Jump to Definition in the Xcode popup menu.
I prefer to keep all the voodoo Amplify knowledge isolated, so I created a User class to interact with AWS Authentication services. I include the error handling portions here in hopes it saves you some grief.
// At the top of the file, outside the class:
import Amplify
import AWSCognitoAuthPlugin
import AWSPluginsCore
import Foundation
// This typealias allows other modules to interpret error responses
// from the AWSCognitoAuthError enum, such as ".usernameExists" without
// needing to import Amplify everywhere.
typealias AWSError = AWSCognitoAuthError
// Amplify Auth methods return AuthError type errors, which are complex multilayer
// enums that require complex deciphering if you want to extract a simple error
// to guide the user.
//
// This custom Error type allows callers from other modules to receive a simple
// explanatory "message" string, which is pulled from the .errorDescription
// property of an Amplify AuthError. The "error" optional is the returned
// .underlyingError property cast to an AWSCognitoAuthError type, which is an
// enum of all the possible problems interacting with Amplify Auth such as
// .usernameExists, .userNotConfirmed, .codeMismatch, or .codeExpired.
struct UserError: Error {
let message: String
let error: AWSError?
init(message: String?, error: Error?) {
self.message = message ?? "Unknown error"
self.error = error as? AWSError
}
}
// Example of usage, within the class. In my case the class signIn()
// function includes an onCompletion callback parameter to allow the
// caller to inform the user and change the UI according to the results.
func signIn(username: String, password: String,
onCompletion: @escaping (Result<Bool, UserError>) -> Void) {
_ = Amplify.Auth.signIn(username: username, password: password) { result in
switch result {
case .success(let signInResult):
onCompletion(.success(signInResult.isSignedIn))
case .failure(let authError):
let awsError = authError.underlyingError as? AWSError ?? AWSError.userNotFound
let userError = UserError(message: authError.errorDescription,
error: awsError)
onCompletion(.failure(userError))
}
}
}
In your view controller, here is how you might process the response from the example signIn() User class method above:
// Inside e.g. an @IBAction function
User.sharedUser.signIn(email, password: password, onCompletion: { result in
switch result {
case .success:
// Go to root View Controller
case .failure(let userError):
if let authError = userError.error {
if (authError == .userNotConfirmed) {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "ShowConfirm", sender: nil)
}
return
}
}
let alert = UIAlertController(title: userError.message, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
DispatchQueue.main.async {
self.present(alert, animated: true, completion: nil)
}
}
})