Buy
Buy

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

If we enter an email that doesn't exist, we get this

Username could not be found

error message. And, as we saw a moment ago, if we return false from checkCredentials(), the error is something about "Invalid credentials".

The point is, depending on where authentication fails, the user will see one of these two messages.

The question now is, what if we want to customize those? Because, username could not be found? Really? In an app that doesn't use usernames!? That's... confusing.

Customizing Error Messages

There are two ways to control these error messages. The first is by throwing a very special exception class from anywhere in your authenticator. It's called CustomUserMessageAuthenticationException. When you do this, you can create your own message. We'll do this later when we build an API authenticator.

The second way is to translate this message. No, this isn't a tutorial about translations. But, if you look at your login template, when we print this error.messageKey thing, we are already running it through Symfony's translation filter:

... lines 1 - 10
{% block body %}
<form class="form-signin" method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
... lines 16 - 29
</form>
{% endblock %}

Another way to look at this is on the web debug toolbar. See this little translation icon? Click that! Cool: you can see all the information about translations that are being processed on this page. Not surprisingly - since we're not trying to translate anything - there's only one: "Username could not be found."... which... is being translated into... um... "Username could not be found."

Internally, Symfony ships with translation files that will translate these authentication error messages into most other languages. For example, if we were using the es locale, we would see this message in Spanish.

Ok, so, why the heck do we care about all of this? Because, the errors are passed through the translator, we can translate the English into... different English!

Check this out: in your translations/ directory, create a security.en.yaml file:

... lines 1 - 10
{% block body %}
<form class="form-signin" method="post">
{% if error %}
<div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}
... lines 16 - 29
</form>
{% endblock %}

This file is called security because of this security key in the translator. This is called the translation "domain" - it's kind of a translation category - a way to organize things.

Anyways, inside the file, copy the message id, paste that inside quotes, and assign it to our newer, hipper message:

Oh no! It doesn't look like that email exists!

"Username could not be found.": "Oh no! It doesn't look like that email exists!"

That's it! If you go back to your browser and head over to the login page, in theory, if you try failing login now, this should work instantly. But... no! Same message. Today is not our lucky day.

This is thanks to a small, um, bug in Symfony. Yes, yes, they do happen sometimes, and this bug only affects our development... slightly. Here's the deal: whenever you create a new translation file, Symfony won't see that file until you manually clear the cache. In your terminal, run:

php bin/console cache:clear

When that finishes, go back and try it again: login with a bad email and... awesome!

Logging Out

Hey! Our login authentication system is... done! And... not that I want to rush our moment of victory - we did it! - but now that our friendly alien users can log in... they'll probably need a way to log out. They're just never satisfied...

Right now, I'm still logged in as [email protected]. Let's close a few files. Then, open SecurityController. Step 1 to creating a logout system is to create the route. Add public function logout():

... lines 1 - 8
class SecurityController extends AbstractController
{
... lines 11 - 30
public function logout()
{
... line 33
}
}

Above this, use the normal @Route("/logout") with the name app_logout:

... lines 1 - 5
use Symfony\Component\Routing\Annotation\Route;
... lines 7 - 8
class SecurityController extends AbstractController
{
... lines 11 - 27
/**
* @Route("/logout", name="app_logout")
*/
public function logout()
{
... line 33
}
}

And this is where things get interesting... We do need to create this route... but we don't need to write any logic to log out the user. In fact, I'm feeling so sure that I'm going to throw a new Exception():

will be intercepted before getting here

... lines 1 - 8
class SecurityController extends AbstractController
{
... lines 11 - 27
/**
* @Route("/logout", name="app_logout")
*/
public function logout()
{
throw new \Exception('Will be intercepted before getting here');
}
}

Remember how "authenticators" run automatically at the beginning of every request, before the controllers? The logout process works the same way. All we need to do is tell Symfony what URL we want to use for logging out.

In security.yaml, under your firewall, add a new key: logout and, below that, path set to our logout route. So, for us, it's app_logout:

security:
... lines 2 - 8
firewalls:
... lines 10 - 12
main:
... lines 14 - 19
logout:
path: app_logout
... lines 22 - 36

That's it! Now, whenever a user goes to the app_logout route, at the beginning of that request, Symfony will automatically log the user out and then redirect them... all before the controller is ever executed.

So... let's try it! Change the URL to /logout and... yes! The web debug toolbar reports that we are once again floating around the site anonymously.

By the way, there are a few other things that you can customize under the logout section, like where to redirect. You can find those options in the Symfony reference section.

But now, we need to talk about CSRF protection. We'll also add remember me functionality to our login form with almost no effort.

Leave a comment!

  • 2019-01-17 Diego Aguiar

    Hey Emin

    Thanks for sharing it, that's a useful workaround when the "clear cache" command doesn't work properly

    Cheers!

  • 2019-01-15 Emin

    Hey!

    for those who don't see changes after cache:clear try rm -rf var/cache/* in the directory you are working from. Apperently sometimes symfony doesn't delete the cache, so you have to manually remove it in order to make it work :)

    Cheers!

  • 2018-12-17 Victor Bocharsky

    Hey Usuri,

    Yes, it's possible. You just need to register a listener. Actually, you can take a look at Symfony Demo: https://github.com/symfony/... - it has something similar to what you're looking for.

    Cheers!

  • 2018-12-15 Usuri

    Is it possible to change locale language automatically depends of user language detected in cookies?

  • 2018-11-28 Victor Bocharsky

    Hey Alex,

    Haha, yeah, "symfony/translation" is required by "knplabs/knp-time-bundle" that we have installed as a dependency, so if you don't install "knp-time-bundle" - you need to "composer require symfony/translation" manually. Thanks for this tip!

    Cheers!

  • 2018-11-27 Alex Finnarn

    I may have fudged a little bit when setting up the initial dependencies, but I had to require the translation component, "composer require symfony/translation", in order to see the translation tab in the Web Profiler and use the translation service...just FYI in case someone else doesn't see the translation tab when you get to that step.

  • 2018-10-05 Diego Aguiar

    Hey Ahmad Mayahi

    Do you have sessions enabled in your project? if so, it should be done automatically

    Cheers!

  • 2018-10-05 Ahmad Mayahi

    The AUTHENTICATION_ERROR will not be saved in the session, so I have to set it manually in onAuthenticationFailure method:


    $request->getSession()->set(Security::AUTHENTICATION_ERROR, $exception);


    Any idea why?

  • 2018-09-27 Victor Bocharsky

    Hey Ahmed,

    Oh, wait, but Symfony Form Component does not translate the input unless you do it by yourself, i.e. manually user |trans Twig filter for the input value. It translates only form field labels. Or do you use Google Translator to translate the whole page? Could you show me this field HTML layout?

    Cheers!

  • 2018-09-26 Ahmed Wali

    Hello Victor Bocharsky,
    My problem is when i set locale: 'ar'
    and go to my product edit form i found the value of the input like this ١٢٠ instead of 120 and the value doesn't appear and when submit it's not validated

  • 2018-09-26 Victor Bocharsky

    Hey Ahmed,

    If you do not want to translate field labels, you can set translation_domain to false, see: https://symfony.com/doc/cur...

    Cheers!

  • 2018-09-25 Ahmed Wali

    Hello Ryan,
    How to disable translate in input value

  • 2018-09-24 Stéphane

    Thank for your advice. It's working perfectly now.

  • 2018-09-24 Victor Bocharsky

    Hey Stephane,

    Did you enable translator in your project? If translator is enabled, then @niumis advice below may help, try to change locale value in "config/services.yaml"

    Cheers!

  • 2018-09-23 niumis

    Hello, Stéphane,
    Change in services.yaml 'locale' parameter ;)

  • 2018-09-22 Stéphane

    Hey Ryan,
    I try to change the default_locale in framework.yaml config file for translate message in french but it is not working. I don't understand why. I have php-intl extension installed.