Buy

Logging Out and Cleaning Up

What about logging out? Symfony has some magic for that too!

Look at the logout part of security.yml:

# app/config/security.yml
# ...
firewalls:
    secured_area:
        # ...
        logout:
            path:   _demo_logout
            target: _demo

path is the name of your logout route. Set it to logout - we’ll create that route in a second. target is where you want to redirect the user after logging out. We already have a route called event, which is our event list page. Use that for target:

# app/config/security.yml
# ...
firewalls:
    secured_area:
        # ...
        logout:
            path:   logout # a route called logout
            target: event  # a route called event

To make the logout route, let’s add another method inside SecurityController and use the @Route annotation:

// ...
// src/Yoda/UserBundle/Controller/SecurityController.php

/**
 * @Route("/logout", name="logout")
 */
public function logoutAction()
{
}

Just like with the loginCheckAction, the code here won’t actually get hit. Instead, Symfony intercepts the request and processes the logout for us.

Try it out by going to /logout manually. Great! As you can see by the web debug toolbar, we’re anonymous once again.

Cleaning up loginAction

If we fail login, we see a “Bad Credentials” message. When Symfony handles the login, it saves this error to the session under a special key, and we’re just fetching it out in loginAction.

Actually, we have more code than we need here. Remove the if statement and just leave the second part:

// src/Yoda/UserBundle/Controller/SecurityController.php
// ...

public function loginAction(Request $request)
{
    $session = $request->getSession();

    // get the login error if there is one
    $error = $session->get(SecurityContextInterface::AUTHENTICATION_ERROR);
    $session->remove(SecurityContextInterface::AUTHENTICATION_ERROR);

    return array(
        // last username entered by the user
        'last_username' => $session->get(SecurityContextInterface::LAST_USERNAME),
        'error'         => $error,
    );
}

The first part isn’t used unless you reconfigure how Symfony sends you to the login page.

Note

The configuration I’m talking about here is the use_forward, which causes Symfony to forward to the login page, instead of redirecting.

Adding CSS to a Single Page

I know I know, the login page is embarrassing looking. So I made a login.css file to fix things - find it in the resources/episode2 directory of the code download.

Let’s move it into a Resources/public/css directory in the UserBundle.

/* src/Yoda/UserBundle/Resources/public/css/login.css */
.login {
    width: 500px;
    margin: 100px auto;
}

/* for the rest of login.css, see the code download */

Just like in episode 1, run app/console assets:install and add the --symlink option, unless you’re on Windows:

php app/console assets:install --symlink

This creates a symbolic link from web/bundles/user to the Resources/public directory in UserBundle. Since web/ is our application’s document root, this makes our new CSS file accessible in a browser by going to /bundles/user/css/login.css.

So how can we add this CSS file to only this page? First, open up the base template. Here, we have a bunch of blocks, including one called stylesheets. All of our global CSS link tags live inside of it:

# app/Resources/views/base.html.twig
# ...

{% block stylesheets %}
    {% stylesheets
        'bundles/event/css/event.css'
        'bundles/event/css/events.css'
        'bundles/event/css/main.css'
        filter='cssrewrite'
    %}
        <link rel="stylesheet" href="{{ asset_url }}" />
    {% endstylesheets %}
{% endblock %}

Let’s override this block in login.html.twig and add the new link tag to login.css:

{# src/Yoda/UserBundle/Resources/views/Security/login.html.twig #}

{% block stylesheets %}
    <link rel="stylesheet" href="{{ asset('bundles/user/css/login.css') }}" />
{% endblock %}

Cool, but do you see the problem? This would entirely replace the block, but we want to add to it. The trick is the Twig parent() function. By including this, all the parent block’s content is included first:

{# src/Yoda/UserBundle/Resources/views/Security/login.html.twig #}

{% block stylesheets %}
    {{ parent() }}

    <link rel="stylesheet" href="{{ asset('bundles/user/css/login.css') }}" />
{% endblock %}

Refresh now. Much less embarrassing looking. When you need to add CSS or JS to just one page, this is how you do it.

And by adding a little error class, it looks even better:

{# src/Yoda/UserBundle/Resources/views/Security/login.html.twig #}
{# ... #}

{% if error %}
    <div class="error">{{ error.message }}</div>
{% endif %}

And while we’re making things look better, let’s open up base.html.twig and add a link tag to the Bootstrap CSS file. Just use a CDN URL for simplicity:

{# app/Resources/views/base.html.twig #}
{# ... #}

{% block stylesheets %}
    <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css"/>

    ...
{% block stylesheets %}

Back in login.html.twig, I’ll tweak the submit button so things look nicer:

{# src/Yoda/UserBundle/Resources/views/Security/login.html.twig #}
{# ... #}

<hr/>
<button type="submit" class="btn btn-primary pull-right">login</button>

Refresh! Ah, much better. I’m a programmer, but I don’t want the site to look totally embarrassing!

Translating the Login Error Message

While we’re here, let’s change that “Bad Credentials” message, it’s a little, “programmery”. The message comes from deep inside Symfony. So to customize it, we’ll use the translator.

First, use the Twig trans filter on the message:

{# src/Yoda/UserBundle/Resources/views/Security/login.html.twig #}
{# ... #}

{% if error %}
    <div class="error">{{ error.message|trans }}</div>
{% endif %}

Next, create a translation file in app/Resources/translations/messages.en.yml. This file is just a simple key-value pair of translations:

# app/Resources/translations/messages.en.yml
"Bad credentials": "Wrong password bro!"

Now, we just need to activate the translation engine in app/config.yml:

framework:
    # ...
    translator:      { fallback: %locale% }

Ok now, try it! Again, so much better!

Leave a comment!

  • 2016-08-16 weaverryan

    Hey PJoy!

    I just replied on your previous comment, but you're right! If you're using Symfony 3, then some parts of this tutorial are out-of-date. The Symfony 3 version of this tutorial is here: http://knpuniversity.com/scree...

    But, I need to make sure you're happy and have exactly what you need :). So, I'll send you an email shortly so we can make sure you're taken care of.

    Cheers!

  • 2015-12-16 Roman

    Hej, Ryan
    I figured out what's going on. I was using filter in login template with this "error.message" from symfony.com, This filter using default security messages. Now everything is ok

  • 2015-12-13 weaverryan

    Hey Roman!

    What message is printing out? "Bad Credentials."? If so, the m(obviously) make sure that's exactly the string you have in your translation file. And then, try two other things:

    1) Make sure the translation system is activated by uncommenting out the framework.translator key in config.yml (it's that *last* code block on this page)

    2) Clear your cache: sometimes when adding your very first translation file in a directory, Symfony won't see it until you clear your cache.

    Let me know if that helps!

  • 2015-12-12 Roman

    For me translations doesn't work either with dot or without it . Using Symfony 2.7.6

  • 2015-04-08 weaverryan

    Hey Luken!

    Very nice catch! And yes, the dot makes all the difference! This change (yes, the dot!) was introduced in Symfony 2.6 - 2.5 and before do not have the dot :) https://github.com/symfony/sym...

    Cheers!

  • 2015-04-03 Luken

    "Bad credentials": "Wrong password bro!" - actually won't work, there should be: "Bad credentials.": "Wrong password bro!" - a difference is a dot. I don't know if this is typo or they changed this string in newer version of Symfony2.