At first, I wanted AuthenticationError to be a class not extending any Exception class. It received the Map<Object, String> and the Throwable to keep the error stack.
Then I've found that many calls from our *Request
classes instantiated a new Auth0Exception giving a simple message like "cannot parse this JSON" or "invalid response" instead of the values map, so I needed to add that String as another parameter. Maybe in a second constructor.
It seems better to extend Auth0Exception
so the default getMessage()
and getCause()
were there already. I thought about creating an interface AuthenticationError
with the common error methods, like getError()
, getDescription()
and the rest of the values inside a map getValues()
. So I ended up with a class that extends Auth0Exception and implements AuthenticationError. ---> AuthenticationException
.
I tried making the callback.onFailure
return an AuthenticationError
(interface) object, but that way I was loosing access to the exceptions stack, maybe useful for the dev. The solution was either add the method getCause()
to the interface OR just making the callback.onFailure
return AuthenticationException
instead.
All the callback.onFailure
calls (async) will now return AuthenticationException
instead of Auth0Exception
.
AuthenticationError
is an interface with the following methods:
public interface AuthenticationError {
String ERROR_KEY = "error";
String ERROR_DESCRIPTION_KEY = "error_description";
String getError();
String getDescription();
Map<String, Object> getValues();
}
AuthenticationException
is a class that extends Auth0Exception
, implements AuthenticationError
and has 2 constructors:
public AuthenticationException(Auth0Exception exception) {
//Used for methods that required to include a hardcoded message like "cannot parse JSON".
super(exception.getMessage(), exception);
}
public AuthenticationException(APIException exception) {
//Used for methods that could parse the response body successfully and give me the map with values.
super(null, exception);
this.error = (String) exception.getResponseError().remove(ERROR_KEY);
this.description = (String) exception.getResponseError().remove(ERROR_DESCRIPTION_KEY);
this.values = exception.getResponseError();
}
Because APIException
already exists in the current implementation, and it retrieves the map with the values received in the response, we can wrap that result and transform it to AuthErrorCodes, generating specific messages for the user to understand what had happened.
If the message is specified in the constructor, getMessage()
will return that. If no message is specified, getMessage()
will return the corresponding AuthErrorCode specific message.
@Override
public String getMessage() {
if (message != null){
return message;
}
//Parse code and error_description to understand what kind of error is this.
return generateMessage();
}
Also, because this lib is meant to be only for Android, we can make use of the @IntDef
annotation and define the Error Codes we know the Authentication API can return. Something like this:
@IntDef({UNKNOWN, UNAUTHORIZED, USER_EXISTS, INVALID_CREDENTIALS, MFA_INVALID, MFA_REQUIRED, MFA_NOT_ENROLLED})
@Retention(RetentionPolicy.SOURCE)
public @interface AuthErrorCode {
int UNKNOWN = 0;
int UNAUTHORIZED = 1;
int USER_EXISTS = 2;
int INVALID_CREDENTIALS = 3;
int MFA_INVALID = 4;
int MFA_REQUIRED = 5;
int MFA_NOT_ENROLLED = 6;
}
This can be accessed with a getter defined on the AuthenticationError
interface.
-
If we are thinking on adding the Management API later, the current callback class is not enough, as it's now making all
*Request
classes to always returnAuthenticationException
(specific for authentication) instead ofAuth0Exception
(generic one) whencallback.onFailure
is called. We should create a new Callback class for the AuthenticationAPI and another callback class for the Management API, and make each API Client use the appropriate one. -
Error message texts in the new
auth0
module would be hardcoded in a class, rather than usingandroid.R.string
. The dev will need to ask for the AuthErrorCode type and specify a resource with the message for that error. This is because this module will eventually be moved outsidelock.android
repository, and also because it's responsibility ofLock
to notify the user of events it considers important.