Skip to content

[Security] User is loaded on every request #43648

Closed
@stlrnz

Description

@stlrnz

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.

return new PreAuthenticatedToken($passport->getUser(), null, $firewallName, $passport->getUser()->getRoles());

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions