How to get auth error from sign in/sign up AWS Amplify?

Viewed 1349

I am currently trying to get what type of error is thrown when a user attempts to sign in/ registers but my switch case is not working because I don't know what enum I am supposed to compare to. Once the error can be determined the app displays a UIAlertController explaining the error. If I print(err.code) I just get an Int back. Can anyone steer me in the right direction? I couldn't find any docs on how to handle it.

func signIn(username: String, password: String) {
    Amplify.Auth.signIn(username: username, password: password) { result in
        switch result {
        case .success:
            print("Sign in succeeded")
            //Go to root vc
        case .failure(let error):
            print("Sign in failed \(error)")
     if let err = error as NSError?{
          switch err.code {
                
          case AWSCognitoIdentityProviderErrorType.unknown.rawValue:
                        self.presentAlert(errorTitle: "Unkown Error", errorMessage: "An unknown error has occured", buttonText: "Ok")
                        print("Unkown error")
          case AWSCognitoIdentityProviderErrorType.invalidPassword.rawValue:
                        self.presentAlert(errorTitle: "Invalid Password", errorMessage: "You have entered an invalid password", buttonText: "Try Again")
          case AWSCognitoIdentityProviderErrorType.tooManyFailedAttempts.rawValue:
                        self.presentAlert(errorTitle: "Excedded login trys", errorMessage: "You attempted to login too many times", buttonText: "Try Again Later")
          case AWSCognitoIdentityProviderErrorType.userNotFound.rawValue:
                        self.presentAlert(errorTitle: "Unknown Credentials", errorMessage: "No user exists with the credentials you entered.", buttonText: "Try Again")
                    default:
                        break
                    }
                }
        }
    }
}
1 Answers

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)
            }
    }
})
Related