How to customize Amplify Auth Error Messages - ios

I am attempting to implement amplify auth on iOS, and what I would like to be able to do is customize the error message that is displayed to a user when authentication fails, as the default error messages are not end-user friendly, but I have no idea how to do this.
For instance, my signIn method is as follows:
func signIn(username: String) {
Amplify.Auth.signIn(username: username, password: "bla") { [weak self] result in
switch result {
case .success (let result):
if case .confirmSignInWithCustomChallenge(_) = result.nextStep {
DispatchQueue.main.async {
self?.showConfirmationSignInView()
}
} else {
print("Sign in succeeded")
}
case .failure(let error):
print (error)
}
}
}
Now in the .failure case, instead of printing the error, I would ideally like to determine if the error is a userNotFound error, or something else. I can't find any info in the docs on this. Any help would be appreciated.

You can do it by checking the error.code. for example, for a user who did not confirm the email if he tries to login then error.code will have UserNotConfirmedException string value. Amplify auth returns different exception codes for different types of errors. You can see all the exceptions from this link. Although it is for flutter, the exception code is identical for any framework. I have used these exception codes in react.

Related

"Cannot Parse Response" error when logging in from certain countries - MongoDB Atlas Device Sync (prev. Realm Sync)

I'm using MongoDB's Atlas Device Sync (until recently it was called Realm Sync) to handle login for my iOS app, coded in Swift.
I am UK based, and the app works fine for users in the UK. However, I recently sent the app to contacts in Eastern Europe (Poland, Belarus, potentially other countries as well. One person also tried logging in using a French VPN apparently) and they've all received the same error when creating an account or logging in with an already created account.
The localised description of this error is "cannot parse response".
Unfortunately I am based in the UK so I can't replicate it on my own device. However, I know that the error when creating an account is being thrown from the below code:
app.emailPasswordAuth.registerUser(email: email!, password: password!, completion: { [weak self] (error) in
DispatchQueue.main.async {
guard error == nil else {
self!.signUpFailed(with: error!)
return
}
self!.signIn(with: self!.email!, and: self!.password!)
}
})
And I know that the error when logging in to an already created account is being thrown from the below code:
app.login(credentials: Credentials.emailPassword(email: email, password: password)) { [weak self] (result) in
DispatchQueue.main.async {
switch result {
case .failure(let error):
self!.signInFailed(with: error)
return
case .success(let user):
self!.continueLoggingIn()
}
}
}
I'm at a bit of a loss here. I have no idea why the response can be parsed in the UK but not other countries. I assume it's an issue with Mongo/Realm but I could be wrong. If anyone can shed any light it would be greatly appreciated.

The operation couldn’t be completed. (AWSMobileClient.AWSMobileClientError error 20.)

I'm currently running into this error when implementing the AWSMobileClient signUp function. I haven't really altered the code sample from the AWS page describing how to implement it, other than changing the attributes to fit my user pool attribute requirements.
First in viewDidLoad, I initialize the mobile client like so:
AWSMobileClient.sharedInstance().initialize { (userState, error) in
if let userState = userState {
print("UserState: \(userState.rawValue)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
}
Then I have the function for signing up. This is what the code looks like (I encapsulate this in a function called signUpUser):
AWSMobileClient.sharedInstance().signUp(username: userEmail,
password: userPass,
userAttributes: ["email":userEmail, "given_name":userFirstName, "family_name": userLastName, "custom:school":userSchool]) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
print("User is signed up and confirmed.")
case .unconfirmed:
print("User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case .unknown:
print("Unexpected case")
}
} else if let error = error {
if let error = error as? AWSMobileClientError {
switch(error) {
case .usernameExists(let message):
print(message)
default:
break
}
}
print("\(error.localizedDescription)")
}
When I run the app on my iPhone, I call this function when the "Sign Up Button" is clicked. In the debug window, I get the following error:
The operation couldn’t be completed. (AWSMobileClient.AWSMobileClientError error 20.)
That's the only info that appears in the Xcode console. Does anyone know how to go about debugging or fixing this?
EDIT: I'm not sure what the issue was that caused this error. I started a fresh project, set up a new cognito pool and backend services, and ported over the code from this project, which resulted in everything working perfectly. The error may have been from incorrectly setting up the user pool, or perhaps not allowing unauthorized access to the sign up function (not sure if I had that set to "No").
If you exhaust the rest of the switch case there, you will be able to see what exactly is the error coming back from the service.
reference: https://stackoverflow.com/a/59521025/2464632

Problems With Error Casting In Swift

Not sure if this a bug or an intended feature.
To create a user with an email and password in Firebase, I've been using the following code:
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if let error = error {
guard let error = error as? FIRAuthErrorCode else { return } // ALWAYS FAILS
...code...
}
...code...
}
The error parameter in the completion handler for the method cannot be cast as FIRAuthErrorCode; it always fails. Is that a bug, or is that the expected behaviour?
Edit: I am aware that error codes can be used to distinguish between the different types of FIRAuthErrorCode errors. It's just not readable, and it doesn't make much sense for the error parameter in the completion handler to be not of be of type FIRAuthErrorCode. The cases and error codes of FIRAuthErrorCode can be found here.
Have you tried using guard let error = error as! FIRAuthErrorCode else { return } to force the casting and check whether the return is nil or not?
After contacting Firebase support about the issue, they've said that the errors that are passed back in completion handlers are just Error objects. They weren't FIRAuthErrorCode objects. To test for the various FIRAuthErrorCode cases, one would have to do something like this:
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if let error = error {
guard let error = FIRAuthErrorCode(rawValue: error._code) else {
fatalError("This should never be executed")
}
switch error {
case .errorCodeInvalidEmail: ...
case .errorCodeWrongPassword: ...
default: ...
}
...code...
}
...code...
}
^ This preserves readability and makes error handling more intuitive. And, there is no need for error casting!
You should check the documentation of signIn method in firebase in order to check the all possible type of error this method can send and then check those type of errors in guard block in your code.
Try this, it's how I do my login and it seems to work great.
FIRAuth.auth()?.signIn(withEmail: EmailField.text!, password: PasswordField.text!, completion: { user, error in
if error == nil {
print("Successfully Logged IN \(user!)")
self.performSegue(withIdentifier: "Login", sender: self)
}
})
I just check that there's not an error with the signin process, then perform my segue.

Using retryWhen to update tokens based on http error code

I found this example on How to refresh oauth token using moya and rxswift which I had to alter slightly to get to compile. This code works 80% for my scenario. The problem with it is that it will run for all http errors, and not just 401 errors. What I want is to have all my other http errors passed on as errors, so that I can handle them else where and not swallow them here.
With this code, if I get a HttpStatus 500, it will run the authentication code 3 times which is obviously not what I want.
Ive tried to alter this code to handle only handle 401 errors, but it seem that no matter what I do I can't get the code to compile. It's always complaining about wrong return type, "Cannot convert return expression of type Observable<Response> to return type Observable<Response>" which makes no sense to me..
What I want: handle 401, but stop on all other errors
import RxSwift
import KeychainAccess
import Moya
public extension ObservableType where E == Response {
/// Tries to refresh auth token on 401 errors and retry the request.
/// If the refresh fails, the signal errors.
public func retryWithAuthIfNeeded() -> Observable<E> {
return self.retryWhen {
(e: Observable<ErrorType>) in
return Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 })
.flatMap { i in
return AuthProvider.sharedInstance.request(
.LoginFacebookUser(
accessToken: AuthenticationManager.defaultInstance().getLoginTokenFromKeyChain(),
useFaceBookLogin: AuthenticationManager.defaultInstance().isFacebookLogin())
)
.filterSuccessfulStatusCodes()
.mapObject(Accesstoken.self)
.catchError {
error in
log.debug("ReAuth error: \(error)")
if case Error.StatusCode(let response) = error {
if response.statusCode == 401 {
// Force logout after failed attempt
log.debug("401:, force user logout")
NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
}
}
return Observable.error(error)
}.flatMapLatest({
token -> Observable<Accesstoken> in
AuthenticationManager.defaultInstance().storeServiceTokenInKeychain(token)
return Observable.just(token)
})
}
}
}
}
Compilation Error
Which line has the compilation error? It seems to me that it would be this line:
.catchError {
error in
//...
return Observable.error(error) // is this the line causing the compilation error?
}
If so, it's probably because catchError is expecting the block to return an Observable<Response> with which it can continue in case of an error, and not an Observable<ErrorType>.
In either case, it helps to annotate your code with more types so that you can pinpoint problems like this, as well as help the Swift compiler, which often can't figure out these kinds of things on its own. So something like this would have helped you:
.catchError {
error -> Observable<Response> in
//...
return Observable.error(error) // Swift should have a more accurate and helpful error message here now
}
Note that I'm only showing you what the error is and how to get Xcode to give you better error messages. What you're trying to return still isn't correct.
Only retry on 401
I'm not sure why you're expecting this code to treat 401 differently (other than posting to the notification center and logging). As it is, you're catching the error, but you're always returning an Observable with an Error event at the end (return Observable.error(error)), so it will never retry.
To get 401 to retry, you should return an Observable from the retryWhen block, which will send a Next event (signifying that you want to retry). For all other status codes, that Observable should send an Error (as you're currently doing), which will signify that you don't want to retry, and that you'd like the error propagated.
So something like this:
.retryWhen { errorObservable -> Observable<ErrorType> in
log.debug("ReAuth error: \(error)")
if case Error.StatusCode(let response) = error where response.statusCode == 401 {
log.debug("401:, force user logout")
NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
// If `401`, then return the `Observable<ErrorType>` which was given to us
// It will emit a `.Next<ErrorType>`
// Since it is a `.Next` event, `retryWhen` will retry.
return errorObservable
}
else {
// If not `401`, then `flatMap` the `Observable<ErrorType>` which
// is about to emit a `.Next<ErrorType>` into
// an `Observable<ErrorType>` which will instead emit a `.Error<ErrorType>`.
// Since it is an `.Error` event, `retryWhen` will *not* retry.
// Instead, it will propagate the error.
return errorObservable.flatMap { Observable.error($0) }
}
}
When you catchError, if it isn't a 401 error, then you simply need to throw the error. That will send the error down the pipe.
There's a different solution to solve this problem without using Observable. It's written on pure RxSwift and returns a classic error in case of fail.
The easy way to refresh session token of Auth0 with RxSwift and Moya
The main advantage of the solution is that it can be easily applicable for different services similar to Auth0 allowing to authenticate users in mobile apps.

Error handler not called for promise

I have a service, which fails when I enter bad login credentials. However, my Promise error handler does not get called.
I don't seem to grasp what's wrong with my code so that the error callback is never reached.
Service
func loadRepositories() -> Promise<[Repository]>{
return Promise { fullfill, reject in
manager.request(Method.GET, baseURL + "/api/1.0/user/repositories")
.authenticate(user: username, password: password)
.responseArray { (response: Response<[Repository], NSError>) in
switch response.result{
case .Success(let value):
fullfill(value)
case .Failure(let e):
// The breakpoint here is reached.
reject(e)
}
}
}
}
Handling
firstly{
service!.loadRepositories()
}.then { repositories -> Void in
loginVC.dismissViewControllerAnimated(true, completion: nil)
self.onLoginSuccessful()
}.always{
// Always gets called
loginVC.isSigningIn = false
}.error { error in
// I never get here even though `reject(e)` is called from the service.
loginVC.errorString = "Login went wrong"
}
By default error does not handle cancellation errors and bad credentials is exactly a cancellation error. If you put print(e.cancelled) before reject(e), you will see that it will return true. If you give a wrong URL for example, you will receive false. In order to get around this, just replace
}.error { error in
with:
}.error(policy: .AllErrors) { error in
and error will be triggered then. In case you use recover, cancellation errors will be handled by default. You can check https://github.com/mxcl/PromiseKit/blob/master/Sources/Promise.swift#L367 for more information.

Resources