symfony: Session is not started when FlashbagInterface is injected by DI and written to

Symfony version(s) affected: Observed in 4.3.3

Description
The session does not seem to be started on actions that return redirect responses. This issue comes to surface rarely because most of the time you will have loaded other pages before you visit an action that needs to both write to the session and redirect to another page.

One example is when someone might be following a link in an email (for example a reset password link or automatic login link) and the link has expired so we would need to redirect to another page and display a flash message.

The scenario above will work if you already have an active session but if you visit the site directly by the link the session isn’t started and the flash messages don’t get displayed. You can get around this by manually starting the session.

How to reproduce

Session does not automatically start:

<?php

namespace App\Controller\User;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;

class ResetPasswordController
{

    /**
     * @var FlashBagInterface
     */
    private $flashBag;

    public function __construct(
        FlashBagInterface $flashBag
    ) {
        $this->flashBag = $flashBag;
    }

    public function __invoke(Request $request): Response
    {

            if (/* link is invalid */) {
                $this->flashBag->add('danger', 'Your reset link has expired or is invalid');
                return new RedirectResponse("/"); //redirects, no flash messages displayed unless session already started
            }
    }
}

Manually starting the session:

<?php

namespace App\Controller\User;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;

class ResetPasswordController
{

    /**
     * @var SessionInterface
     */
    private $session;

    /**
     * @var FlashBagInterface
     */
    private $flashBag;

    public function __construct(
        SessionInterface $session,
        FlashBagInterface $flashBag
    ) {
        $this->session = $session;
        $this->flashBag = $flashBag;
    }

    public function __invoke(Request $request): Response
    {
            $this->session->start(); //start the session manually

            if (/* link is invalid */) {
                $this->flashBag->add('danger', 'Your reset link has expired or is invalid');
                return new RedirectResponse("/"); //works fine now with flash messages
            }
    }
}

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 16 (12 by maintainers)

Commits related to this issue

Most upvoted comments

friendly ping @warslett so that we don’t forget about it 😃

@nicolas-grekas sure I can do that

@Tobion Yes that’s correct. In simple terms: FlashBag is only to be used with session so therefore use it like this:

<?php

namespace App\Controller;

use App\Entity\...;
use App\Form\...;
use App\Repository\...;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpFoundation\Session\Session;

/**
 * @Route("/...")
 */
class ...
{
    /**
     * @var FlashBagInterface
     */
    private $flashBag;

    public function __construct(FlashBagInterface $flashBag)
    {
        $this->flashBag = $flashBag;
    }

    /**
     * @Route("/...", name="...")
     */
    public function ...(... $..., Session $session)
    {
        ...

        $session->getFlashbag()->add('type', 'message');

        return new RedirectResponse(
            $this->router->generate('...')
        );
    }
}

So the problem here really seems to be that people should not inject the FlashBag directly but only get it through the session. Otherwise the bag is not registered with the session at all and writes do not start the session. So the PR https://github.com/symfony/symfony/pull/24200 seems wrong as it encouraged using the flash bag service when it shouldn’t be used at all in user-land.