October: Translate AuthException messages from October\Rain\Auth\Manager

Created on 8 Jun 2016  路  24Comments  路  Source: octobercms/october

What would be the best approach to translate authentication messages like this one
https://github.com/octobercms/library/blob/master/src/Auth/Manager.php#L391

I started customizing the login from the RainLab.User plugin by overriding the components, which worked out pretty well. Then I realized, the Auth facade is a class from the october rain library. Do I have to override the whole facade to translate?
One language would be enough, no need to internationalize so far. Why are these frontend related strings so deep in the core?

Medium Accepted Maintenance

Most helpful comment

from the october documentation https://octobercms.com/docs/services/error-log#exception-handling
In your plugin.php boot(), you can override the hardcoded message like this:

App::error(function(\October\Rain\Auth\AuthException $exception) {
     return Lang::get('your.plugin::lang.authexception');
});

All 24 comments

I assume we would need to catch the exception in the RL.User plugin and provide a translated message in its place. This just hasn't occurred to date.

That wouldn't be a problem to do in the overridden component by myself. But there are multiple:

throw new AuthException(sprintf('Cannot login user "%s" as they are not activated.', $login));
throw new AuthException(sprintf('A user was found to match all plain text credentials however hashed credential "%s" did not match.', $credential));
throw new AuthException("A user was not found with the given credentials.");

and multiple occurences of

throw new AuthException('The password attribute is required.');

which are avoided by the Plugin already (missing credentials are validated by RL.User beforehand)
At least the first three occur regularly while using the components from the RL.User plugin.

As for your suggestion to catch the exception:
The only way to distinguish them would be by the error message, which is kind of hacky. (? regex)

I now did it like this in my mod of RainLab\User\Components\Account in function onSignin for example

try {
    $user = Auth::authenticate($credentials, true);
} catch (\October\Rain\Auth\AuthException $e) {
    $authMessage = $e->getMessage();
    // for error messages see October\Rain\Auth\Manager
    if (strrpos($authMessage,'hashed credential') !== false) {
        $message = 'Translated string for wrong pw.';
    } elseif (strrpos($authMessage,'user was not found') !== false) {
        $message = 'Translated string for user not found';
    } elseif (strrpos($authMessage,'not activated') !== false) {
        $message = 'Translated string for inactive account';
    } else {
        throw $e;
    }
    // now throw custom exception or return something to ajax request
}

But matching against these message snippets doesn't seem right... maybe the October\Rain\Auth\Manager could return some code like user_not_found or how do you usually hand over additional information in an exception? Create custom exceptions for each error?

There are a couple of ways to look at this

1) Do we really need debug auth errors for the front end users? These distinct error messages are for debugging purposes. When debug mode is false, then a single error message is displayed, as seen here: https://github.com/octobercms/library/blob/master/src/Auth/AuthException.php#L22

2) If we do need the debug messages, each error will need its own exception class. An example of this can be found in the Halycon library. These Exceptions are then translated back at the CMS layer, as seen here: https://github.com/octobercms/october/blob/develop/modules/cms/classes/CmsObject.php#L279-L321

Which do you think? Is there value in giving specific authentication error messages on the front end?

What I did, is

  • offer a password reset link on wrong password (on second try) below login form
  • offer to resend activation link email if deactivated accounts below login form
    (doesn't have to be intended behaviour)
  • give a hint about login throttling/suspended account, so user can stop trying

I think this improves the user experience. Anyway, I wasn't aware, that the different messages were due to debug mode. I think there's definitively a value in having different exceptions, at least you can then differentiate if you want to.

If you want to offer all of this - even in the UserPlugin - is of course up to you. I'd be happy when able to modify the plugin component to use mentioned behavior when not in debug mode, right now it's not possible unfortunately.

This is also a small problem for me regarding the backend. Some of my customers can't understand English. It would be very helpful, if the error message for a failed backend login could be translated as well. I can understand, that not every exception gets translated, but mistyping the password once or twice is not so exceptional. It's more like a regular error, comparable to a failed validation of a form field.

@daftspunk Why do we need a different exception class for every exception?

If we do need the debug messages, each error will need its own exception class. An example of this can be found in the Halycon library. These Exceptions are then translated back at the CMS layer, as seen here: https://github.com/octobercms/october/blob/develop/modules/cms/classes/CmsObject.php#L279-L321

Why can't we just do something like throw new ValidationException(Lang::get('system::lang.auth.invalid_login');?

Hello.. i could translate the messages with Javascript and Twig.

you need following code to override the ajaxErrorMessage event:

$(window).on('ajaxErrorMessage', function(event, message){
      var customMessage;
      if(message = 'The details you entered did not match our records. Please double-check and try again.') {
          customMessage = '{{ 'account.user-not-found-translatable-string'|_ }}';
      }
      else if(message = 'next message from AuthException') {
          customMessage = '{{ 'account.nextMessage'|_ }}';
      }
      else {
          customMessage = message;
      }
      alert('{{ 'Error: '|_ }}: ' + customMessage);
      event.preventDefault();
  });

Why do we need a different exception class for every exception?

@LukeTowers, that would be if we wanted the debug messages to be translated, something a normal user should never really see.

@zwieblauch That worked for me. I added your JS snippet to the account/signin.htm partial, which I had already overridden in my theme. Then all I had to do was enter the translation in the newly created key in the backend Translate tab.

We don't need necessarily @luketowers. But if we want to address different types of auth errors reasons with different reactions, we can either do so by checking for different exceptions (which we don't have right now) or by passing error messages directly to the frontend.

If we want to react differently to types of authfailure, we need different exceptions. If we don't have that, we need to use translated errors from core. third option is to handle different auth fails in one way (just "Failed"). I asked about a possibility of the former two.

just to clarify

How can we translate the following message "The details you entered did not match our records. Please double-check and try again." which appears when the user tries to login with an invalid credential?

This message is hardcoded on .../rain/src/Auth/AuthException.php. We don't want to edit the core.

from the october documentation https://octobercms.com/docs/services/error-log#exception-handling
In your plugin.php boot(), you can override the hardcoded message like this:

App::error(function(\October\Rain\Auth\AuthException $exception) {
     return Lang::get('your.plugin::lang.authexception');
});

@shiftaltd Thanks!

Personally, I do not think, beyond debugging purposes, it is ever a good code to give detailed error responses. Not to mention the fact that, with the upcoming GDPR compliance, I am pretty sure that it's not compliant to reveal the fact that you entered a valid email, but the password is wrong...

@datune I think the ideal solution here is to provide a translation for the single error message that gets displayed when the application is in production. Would you like to make a PR for that in the backend module?

@LukeTowers I've tested this locally and it works. Is this what you had in mind?

@datune not quite, the translation needs to happen in the Backend layer, think about the case where the user does not use the Backend module but does use the October\RainAuth package

@datune I'm thinking wrapping https://github.com/octobercms/october/blob/master/modules/backend/controllers/Auth.php#L83-L86 in a try-catch block and rethrowing the exception with the translation when not in debug mode (additionally it would be nice if the exception that gets thrown also returns a 401 response)

Hi @LukeTowers, I would like to able to translate default auth soft error and I have tested @datune solution with Rainlab.User plugin in frontend and translation works fine.

What is needed to move this forward? How can I help?

@jan-vince @datune I've merged in the PRs, thanks!

It is not solved, I still have this eternal problem. Why are error messages hard-coded? What purpose?
OctoberCMS is a super CMS but why for example the message 'Cannot login user "user" as they are not activated.' is not translatable? I do not understand.
What to do? Change the value in the OctoberCMS source code? Obviously not.

@simon511000 you are seeing a debug message, disable debug mode and you will get the translated generic message. We're not going to translate the specific error messages (i.e. debug messages) because we want to encourage people to not have live sites running with debug mode enabled, and detailed login failure messages are nothing but helpful to attackers.

ok thank you very much

@daftspunk @LukeTowers Why use a different Exception class when you already have a custom AuthException class?

Wouldn't it be more effective to have an enum inside it with login_attribute_missing, credential_mismatch, invalid_login, password_required, user_not_activated and then a parameter bag or just a field to hold any variable field (i.e. $credential for credential_mismatch, $loginCredentialKey for attribute_missing, $user for user_not_activated).

On the frontend, a developer could simply catch the AuthException, check what enum category it falls under and have their own lang string handling even in debug mode. As to when the app is in production mode, the same enum will always return i.e. invalid_login. So even here, you will be moving the translation logic out of AuthException class entirely so translation will be out of this library entirely.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jvanremoortere picture jvanremoortere  路  3Comments

mittultechnobrave picture mittultechnobrave  路  3Comments

mittultechnobrave picture mittultechnobrave  路  3Comments

Flynsarmy picture Flynsarmy  路  3Comments

axomat picture axomat  路  3Comments