7

I am just upgrading my symfony 4.4 application to 5.3 to use some new cool stuff (UX, UUID, ..). So I started a new project and ran the make:auth command to create the security components at latest defaults. Everything works perfect, except the remember me functionality. The cookie is just not set (regardless which browser). Maybe you can help me

security.yaml

security:
    enable_authenticator_manager: true
    password_hashers:
        App\Entity\User:
            algorithm: auto

    providers:
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            lazy: true
            provider: app_user_provider
            custom_authenticator: App\Security\LoginFormAuthenticator
            pattern: ^/
            logout:
                path: _logout
                target: _index
            remember_me:
                secret: '%env(APP_SECRET)%'
                lifetime: 31536000 # 1 year in seconds
                always_remember_me: true
                path: _index
            switch_user: true

    role_hierarchy:
        ROLE_USER: ROLE_USER
        ROLE_ADMIN: [ROLE_USER, ROLE_ALLOWED_TO_SWITCH]

    access_control:
        - { path: ^/login, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/reset-password, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/datenschutz, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/impressum, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/worker, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/, role: ROLE_ADMIN }
        - { path: ^/, role: ROLE_USER }

LoginFormAuthenticator

<?php

namespace App\Security;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\CsrfTokenBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Credentials\PasswordCredentials;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\PassportInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
    use TargetPathTrait;

    public const LOGIN_ROUTE = '_login';

    private UrlGeneratorInterface $urlGenerator;

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

    public function authenticate(Request $request): PassportInterface
    {
        $email = $request->request->get('email', '');

        $request->getSession()->set(Security::LAST_USERNAME, $email);

        return new Passport(
            new UserBadge($email),
            new PasswordCredentials($request->request->get('password', '')),
            [
                new CsrfTokenBadge('authenticate', $request->get('_csrf_token')),
            ]
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
            return new RedirectResponse($targetPath);
        }

        return new RedirectResponse($this->urlGenerator->generate('_index'));
    }

    protected function getLoginUrl(Request $request): string
    {
        return $this->urlGenerator->generate(self::LOGIN_ROUTE);
    }

    public function supportsRememberMe(): bool
    {
        return true;
    }
}

login.html.twig

{% extends 'base.html.twig' %}

{% block title %}{{ ('meta.title.login')|trans }}{% endblock %}

{% block body %}
<h1>{{ ('security.login.header')|trans }}</h1>

<form method="post">
    {% if error %}
        <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
    {% endif %}

    <div class="mb-3">
        <label for="inputEmail" class="form-label">{{ ('security.login.email')|trans }}</label>
        <input type="email" value="{{ last_username }}" name="email" id="inputEmail" class="form-control" autocomplete="email" required autofocus>
    </div>

    <div class="mb-3">
        <label for="inputPassword" class="form-label">{{ ('security.login.password')|trans }}</label>
        <input type="password" name="password" id="inputPassword" class="form-control" autocomplete="current-password" required>
    </div>

    <div class="mb-3">
        <a href="{{ path('app_forgot_password_request') }}">
            {{ ('button.forgot_password')|trans }}
        </a>
    </div>

    <input type="hidden" name="_csrf_token" value="{{ csrf_token('authenticate') }}">

    <button class="btn btn-lg btn-outline-success" type="submit">
        {{ ('security.login.button')|trans }}
    </button>
</form>
{% endblock %}

Thanks a lot in advance!!

--------------- EDIT ----------------------

I originally asked this question for version 5.4, but is relevant for 5.3 as well - I tried both without getting the cookie to be set

fehmelchen
  • 203
  • 3
  • 15

2 Answers2

15

I setup a test app and confirmed the remember me cookie was not being sent but then I cheated and saw the hint on the Symfony Slack channel. When using the new passport based authentication system you need to use the remember me badge. It's documented here.

So update your Authenticator::authenticate method with:

        return new Passport(
            new UserBadge($email),
            new PasswordCredentials($request->request->get('password', '')),
            [
                new CsrfTokenBadge('authenticate', $request->get('_csrf_token')),
                new RememberMeBadge(),
            ]
        );

It all seemed to work for me.

Cerad
  • 48,157
  • 8
  • 90
  • 92
0

I had same problem, cookie REMEMBERME was set but not called when PHPSESSID was deleted . After spending time I found in my User entity class this (I added after an upgrade of Symfony without thinking about it) :

public function getUserIdentifier() {
  return $this->id;
}

But in secutiy.yml, my user provider is :

    app_user_provider:
        entity:
            class: App\Entity\User
            property: email

So I changed my getUserIdentifier function to this and it works now :

public function getUserIdentifier() {
  return $this->email;
}
  • Not working in Symfony >=5.3. – long Oct 06 '21 at 19:05
  • This works for me. Since the changes with UserInterface and PasswordAuthenticatedUserInterface in SF 5.3, the comments for the function getUserIdentifier is `Returns the identifier for this user (e.g. its username or email address).`.In the past the function was getUsername but it's deprecated/removed. This was confusing for me. – Dowdow Aug 08 '22 at 04:10