[Symfony 5]Confirmation message after logout

Viewed 3557

On Symfony 5, using the built-in login system, it seems impossible to add a confirmation message after logout. I have followed strictly the steps described on the official website. Unfortunately, the method logout inside the SecurityController is useless. I'm redirected directly on the login page.

Here you will have my security.yaml file :

security:
encoders:
    App\Entity\User:
        algorithm: auto


# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
    # used to reload user from session & other features (e.g. switch_user)
    app_user_provider:
        entity:
            class: App\Entity\User
            property: email
    # used to reload user from session & other features (e.g. switch_user)
firewalls:
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false
    main:
        anonymous: lazy
        provider: app_user_provider
        guard:
            authenticators:
                - App\Security\LoginFormAuthenticator
        logout:
            path: logout
            target: login

        remember_me:
            secret:   '%kernel.secret%'
            lifetime: 604800 # 1 week in seconds
            path:     home
            always_remember_me: true

        # activate different ways to authenticate
        # https://symfony.com/doc/current/security.html#firewalls-authentication

        # https://symfony.com/doc/current/security/impersonating_user.html
        # switch_user: true

# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
    - { path: ^/logout$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/, roles: IS_AUTHENTICATED_FULLY }
    - { path: ^/admin, roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
    - { path: ^/profile, roles: [IS_AUTHENTICATED_FULLY, ROLE_USER] }

And the Controller :

<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        if ($this->getUser()) {
            return $this->redirectToRoute('home');
        }

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();

        return $this->render('security/login.html.twig', ['last_username' => null, 'error' => $error]);
    }

    public function logout()
    {
        throw new \Exception('Don\'t forget to activate logout in security.yaml');
    }
}

?>

Thank you for your help !

5 Answers

For anyone who's wondering how exactly they can implement the external redirect with the new logout customization:

As stated in the documentation, create a new CustomLogoutListener class and add it to your services.yml configuration.

The CustomLogoutListener class should implement the onSymfonyComponentSecurityHttpEventLogoutEvent method, which will receive the LogoutEvent as a parameter, that will allow you to set the response:

namespace App\EventListener;

use JetBrains\PhpStorm\NoReturn;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Event\LogoutEvent;

class CustomLogoutListener
{
    /**
     * @param LogoutEvent $logoutEvent
     * @return void
     */
    #[NoReturn]
    public function onSymfonyComponentSecurityHttpEventLogoutEvent(LogoutEvent $logoutEvent): void
    {
        $logoutEvent->setResponse(new RedirectResponse('https://where-you-want-to-redirect.com', Response::HTTP_MOVED_PERMANENTLY));
    }
}

# config/services.yaml
services:
    # ...
    App\EventListener\CustomLogoutListener:
        tags:
            - name: 'kernel.event_listener'
              event: 'Symfony\Component\Security\Http\Event\LogoutEvent'
              dispatcher: security.event_dispatcher.main

logout method in SecurityController won’t actually get hit because Symfony will intercept the request. if you need to do something after logout you can use logout success handler

namespace App\Logout;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;

class MyLogoutSuccessHandler implements LogoutSuccessHandlerInterface
{
    /**
     * {@inheritdoc}
     */
    public function onLogoutSuccess(Request $request)
    {
        // you can do anything here
        return new Response('logout successfully'); // or render a twig template here, it's up to you
    }
}

and you can register your logout success handler to security.yaml

firewalls:
    main:
        anonymous: lazy
        provider: app_user_provider
        guard:
            authenticators:
                - App\Security\LoginFormAuthenticator
        logout:
            path: logout
            success_handler: App\Logout\MyLogoutSuccessHandler # assume you have enable autoconfigure for servicess or you need to register the handler

Thanks to Indra Gunawan, this solution works. My goal was to redirect to the login page with a message like "You've been successfully logged out".

In that case, the LogoutSuccessHandler must be adapted to route to the login page :

namespace App\Logout;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

class MyLogoutSuccessHandler extends AbstractController implements LogoutSuccessHandlerInterface
{

    private $urlGenerator;

    public function __construct(UrlGeneratorInterface $urlGenerator)
    {
        $this->urlGenerator = $urlGenerator;
    }

    public function onLogoutSuccess(Request $request)
    {
        return new RedirectResponse($this->urlGenerator->generate('login', ['logout' => 'success']));
    }
}

The route login need to be defined in routes.yaml :

login:
    path: /login
    controller: App\Controller\SecurityController::login

logout:
    path: /logout
    methods: GET

In that case, when logout, you will be redirected on an url like : /login?logout=success

Lastely, you can catch logout parameter in twig template like :

    {%- if app.request('logout') -%}
        <div class="alert alert-success">{% trans %}Logout successful{% endtrans %}</div>
    {%- endif -%}   

As of version 5.1 LogoutSuccessHandlerInterface is deprecated, it is recommended to use LogoutEvent.

Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface is deprecated

But there are no examples or information about LogoutEvent in the official documentation

Related