Description
Symfony version(s) affected: 5.3.9
Description
After upgrading my application to Symfony 5.3 I tried to enable the new security system. Everything seems to work well. However, I discovered that a fresh user object is loaded by the UserProvider on every request (although it is still beeing loaded from the session).
I'm using REMOTE_USER authentication and a custom User and UserProvider implementation.
How to reproduce
I created a small dummy project to reproduce the problem: https://github.com/stlrnz/test-new-symfony-security
As you can see, there is nothing special in my implementation/configuration:
<?php
declare(strict_types=1);
namespace App\Security;
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
private $username;
public function __construct(string $username)
{
$this->username = $username;
}
public function getRoles()
{
return ['ROLE_USER'];
}
public function getPassword()
{
return null;
}
public function getSalt()
{
return null;
}
public function eraseCredentials()
{
}
public function getUsername()
{
return $this->username;
}
public function getUserIdentifier()
{
return $this->getUsername();
}
}
<?php
declare(strict_types=1);
namespace App\Security;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
final class UserProvider implements UserProviderInterface
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function refreshUser(UserInterface $user)
{
$this->logger->debug('Refreshing user ' . $user->getUserIdentifier());
return $user;
}
public function supportsClass(string $class)
{
return $class === User::class;
}
public function loadUserByUsername(string $username)
{
$this->logger->debug('Loading user ' . $username);
return new User($username);
}
public function loadUserByIdentifier(string $username)
{
return $this->loadUserByUsername($username);
}
}
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
custom_remote_user_provider:
id: App\Security\UserProvider
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
remote_user:
provider: custom_remote_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# 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: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
Additional context
As you can see the UserProvider writes two log messages for debugging.
When using the new security system the user is loaded on the first request:
Loading user stlrnz
and refreshed and loaded on the following:
Refreshing user stlrnz
Loading user stlrnz
When using the old system by configuring
security:
enable_authenticator_manager: false
the user is still loaded on the first request
Loading user stlrnz
and refreshed on the following (no loading as expected).
Refreshing user stlrnz
Im my real application, loading a user is a very complex operation (requires some webservice calls etc.). And therfore it should not be done on every request. Is there a way to achive this?
I tried to understand why this happens in the new system. It seems that the Authenticator always triggers the load of the user through the passport to create an Authenticated Token.