8000 Symfony custom authenticator still always try to re-authenticate and create a new session for user · Issue #54370 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content
Symfony custom authenticator still always try to re-authenticate and create a new session for user #54370
Closed as not planned
@bauermax

Description

@bauermax

Symfony version(s) affected

6.4.5

Description

Hi everyone,

I'm currently developping a new Symfony 6.4.5 app which needs a custom Authenticator and a custom User Provider.
Basically the reverse proxy behind the application adds a specif http header with a token which should be used by the app to fetch user data through an dedicated external API.

Here is the problem i noticed:

I'm working on mode stateless = false; so i want to authenticate the user and fetch the api only for the first request. The other requests are supposed to retrieve the user from the session but it doesn't behave like that

How to reproduce

Here is the code of my Provider and my custom Authenticator (for my tests, i am very close to the default symfony documentation example):

use App\Security\User\CustomUserProvider;

class CustomUserProvider  im
86C0
plements UserProviderInterface
{

    /**
     * Symfony calls this method if you use features like switch_user
     * or remember_me. If you're not using these features, you do not
     * need to implement this method.
     *
     * @throws UserNotFoundException if the user is not found
     */
    public function loadUserByIdentifier(string $identifier): UserInterface
    {
        dump("load user by identifier");
        $user = new User(123456789,"email",['ROLE_ADMIN','ROLE_USER']); 

        return $user;
        // Load a User object from your data source or throw UserNotFoundException.
        // The $identifier argument is whatever value is being returned by the
        // getUserIdentifier() method in your User class.
        throw new \Exception('TODO: fill in loadUserByIdentifier() inside '.__FILE__);
    }

    /**
     * Refreshes the user after being reloaded from the session.
     *
     * When a user is logged in, at the beginning of each request, the
     * User object is loaded from the session and then this method is
     * called. Your job is to make sure the user's data is still fresh by,
     * for example, re-querying for fresh User data.
     *
     * If your firewall is "stateless: true" (for a pure API), this
     * method is not called.
     */
    public function refreshUser(UserInterface $user): UserInterface
    {
		dump("refresh User",$user);
        if (!$user instanceof UserInterface) {
            throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
        }

        return $user;


        // Return a User object after making sure its data is "fresh".
        // Or throw a UserNotFoundException if the user no longer exists.
        throw new \Exception('TODO: fill in refreshUser() inside '.__FILE__);
    }

    /**
     * Tells Symfony to use this provider for this User class.
     */
    public function supportsClass(string $class): bool
    {
        return User::class === $class || is_subclass_of($class, User::class);
    }

    /**
     * Upgrades the hashed password of a user, typically for using a better hash algorithm.
     */
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
    {
        // TODO: when hashed passwords are in use, this method should:
        // 1. persist the new password in the user storage
        // 2. update the $user object with $user->setPassword($newHashedPassword);
    }

}
<?php


namespace App\Security;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Authenticator\AbstractAuthenticator;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\Passport;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;

class CustomAuthenticator extends AbstractAuthenticator
{
    /**
     * Called on every request to decide if this authenticator should be
     * used for the request. Returning `false` will cause this authenticator
     * to be skipped.
     */
    public function supports(Request $request): ?bool
    {
        return true;
    }

    public function authenticate(Request $request): Passport
    {
        dump("authenticate");
        // implement your own logic to get the user identifier from `$apiToken`
        // e.g. by looking up a user in the database using its API key

        $userIdentifier = "fake id";


        return new SelfValidatingPassport(new UserBadge($userIdentifier));
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        // on success, let the request continue
        return null;
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        $data = [
            // you may want to customize or obfuscate the message first
            'message' => strtr($exception->getMessageKey(), $exception->getMessageData())

            // or to translate this message
            // $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
        ];

        return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
    }
}

(security.yaml)

security:
    # 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:
        # users_in_memory: { memory: null }
        rush_user_provider:
            id: App\Security\Provider\CustomUserProvider
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            stateless: false
            provider: custom_user_provider
            custom_authenticators:
                - App\Security\CustomAuthenticator

            # 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: false

    # 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 }


When i try the two scenarios:
1 (first request for a user)
--> enter in "support" function (twice)
--> enter in authenticate function
--> enter in 'loadUserByIdentifier' function a create the user

This scenario works as expected: The user has no session: he is authenticated and the user is loaded from the API and the stored in session.

2 (others requests)
--> enter in 'refreshUser' function( and the user is correctly retrieved from the session)
--> enter in "support" function (twice)
--> enter in authenticate function (again ?)
--> enter in 'loadUserByIdentifier' function and re-create the user

I was expceting to enter only in 'refreshUser' function when the session exists
I'm not using the stateless: true option in this case.

Is this the normal behavior ?

Possible Solution

No response

Additional Context

I found an old similar issue (which was patched) with the 'REMOTE_USER' provider.
#43648

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

      0