Artggd
Artggd

Reputation: 51

Symfony2 / FOS user bundle - Remember me after registration

I am trying to set the REMEMBERME cookie on user registration. My users are logged on directly after they register, without a confirmation email.

I don't want to use the always_remember_me functionality because I still want the user to opt-in a checkbox.

I found this function called by FOS' RegistrationController:

in FOS\UserBundle\Security\LoginManager

final public function loginUser($firewallName, UserInterface $user, Response $response = null)
{
    $this->userChecker->checkPostAuth($user);

    $token = $this->createToken($firewallName, $user);

    if ($this->container->isScopeActive('request')) {
        $this->sessionStrategy->onAuthentication($this->container->get('request'), $token);

        if (null !== $response) {
            $rememberMeServices = null;
            if ($this->container->has('security.authentication.rememberme.services.persistent.'.$firewallName)) {
                $rememberMeServices = $this->container->get('security.authentication.rememberme.services.persistent.'.$firewallName);
            } elseif ($this->container->has('security.authentication.rememberme.services.simplehash.'.$firewallName)) {
                $rememberMeServices = $this->container->get('security.authentication.rememberme.services.simplehash.'.$firewallName);
            }

            if ($rememberMeServices instanceof RememberMeServicesInterface) {
                $rememberMeServices->loginSuccess($this->container->get('request'), $response, $token);
            }
        }
    }

    $this->securityContext->setToken($token);
}

This seems to call a remember me service and set the token. Unfortunately it's not triggered because I don't have any.

How can I create a remember me service? Is there a more straightforward way to set the REMEMBERME cookie on register?

Thanks.

Upvotes: 3

Views: 833

Answers (1)

Artggd
Artggd

Reputation: 51

It seems to be an old bug, still not fixed since 2012. The solution I found was to implement this PR in my own user bundle.

Note: This works for 1.3, haven't try on 2.0

  1. Add this compiler pass:

/*
 * This file is part of the FOSUserBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace AppUserBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;

/**
 * Registers the additional validators according to the storage
 *
 * @author Vasily Khayrulin <[email protected]>
 */
class InjectRememberMeServicesPass implements CompilerPassInterface
{
    /**
     * {@inheritDoc}
     */
    public function process(ContainerBuilder $container)
    {

        $rememberMeServices = array();
        foreach ($container->getDefinitions() as $id => $definition) {
            if (0 !== strpos($id, 'security.authentication.rememberme.services.')) {
                continue;
            }
            if ($definition->isAbstract()) {
                continue;
            }
            $firewallName = $definition->getArgument(2);
            $rememberMeServices[$firewallName] = new Reference($id);
        }

        $loginManager = $container->getDefinition('fos_user.security.login_manager');
        $loginManager->replaceArgument(4, $rememberMeServices);
    }
}
  1. Instantiate it in your bundle's build:

namespace AppUserBundle;

use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use AppUserBundle\DependencyInjection\Compiler\InjectRememberMeServicesPass;

class AppUserBundle extends Bundle
{
    public function getParent()
    {
        return 'FOSUserBundle';
    }

    public function build(ContainerBuilder $container)
    {
        parent::build($container);
        $container->addCompilerPass(new InjectRememberMeServicesPass());
    }
}
  1. Fully override the LoginManager:

/*
 * This file is part of the FOSUserBundle package.
 *
 * (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace AppUserBundle\Security;

use FOS\UserBundle\Model\UserInterface;
use FOS\UserBundle\Security\LoginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\User\UserCheckerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\RememberMe\AbstractRememberMeServices;
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;

/**
 * Abstracts process for manually logging in a user.
 *
 * @author Johannes M. Schmitt <[email protected]>
 */
class LoginManager implements LoginManagerInterface
{
    private $securityContext;
    private $userChecker;
    private $sessionStrategy;
    private $container;

    /**
     * @var AbstractRememberMeServices[]
     */
    private $rememberMeServices;

    public function __construct(
        SecurityContextInterface $context,
        UserCheckerInterface $userChecker,
        SessionAuthenticationStrategyInterface $sessionStrategy,
        ContainerInterface $container,
        $rememberMeServices
    ) {
        $this->securityContext    = $context;
        $this->userChecker        = $userChecker;
        $this->sessionStrategy    = $sessionStrategy;
        $this->container          = $container;
        $this->rememberMeServices = $rememberMeServices;
    }

    final public function loginUser($firewallName, UserInterface $user, Response $response = null)
    {
        $this->userChecker->checkPostAuth($user);

        $token = $this->createToken($firewallName, $user);

        if ($this->container->isScopeActive('request')) {
            $this->sessionStrategy->onAuthentication($this->container->get('request'), $token);

            if (null !== $response && isset( $this->rememberMeServices[$firewallName] )) {
                $rememberMeServices = $this->rememberMeServices[$firewallName];
                if ($rememberMeServices instanceof RememberMeServicesInterface) {
                    $rememberMeServices->loginSuccess($this->container->get('request'), $response, $token);
                }
            }
        }

        $this->securityContext->setToken($token);
    }

    protected function createToken($firewall, UserInterface $user)
    {
        return new UsernamePasswordToken($user, null, $firewall, $user->getRoles());
    }
}
  1. Also override its service declaration in your services.yml file:

    services:
        # ...
            fos_user.security.login_manager:
                class: AppUserBundle\Security\LoginManager
                arguments:
                    - @security.context
                    - @security.user_checker
                    - @security.authentication.session_strategy
                    - @service_container
                    - { }
    
  2. Finally, add the remember_me checkbox on your register.html.twig template:

    <input type="checkbox" id="remember_me" name="_remember_me" checked />
    <label for="remember_me">Keep me logged in</label>
    

Upvotes: 1

Related Questions