Skip to content

[Security] Cannot autowire custom LoginFormAuthenticator in a controller action #59091

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ebitkov opened this issue Dec 4, 2024 · 6 comments
Closed

Comments

@ebitkov
Copy link

ebitkov commented Dec 4, 2024

Symfony version(s) affected

7.2

Description

This may be related to #59071.

I have a custom authenticator extending the builtin AbstractLoginFormAuthenticator. I autowire it into the register action of my SecurityController, so I can authenticate newly registered users, so they can continue the process they are currently in without the need to verify their data at this point:

class SecurityController extends AbstractController {
    # ...
  
    public function register(
        App\Security\LoginFormAuthenticator $customAuthenticator,
        # ...
    ) {
        # ...
    }

After upgrading to 7.2, this throws an exception:

App\Controller\SecurityController::register(): Argument #3 ($customAuthenticator) must be of type App\Security\LoginFormAuthenticator, Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator given

A quick debug of the service shows, that the dev environment seems to wrap it automatically into the TracableAuthenticator class:

> symfony console debug:container App\Security\LoginFormAuthenticator


// This service is a private alias for the service debug.App\Security\LoginFormAuthenticator                           

Information for Service "debug.App\Security\LoginFormAuthenticator"
===================================================================

 Collects info about an authenticator for debugging purposes.

 ---------------- ----------------------------------------------------------------------------------------------------------------------- 
  Option           Value                                                                                                                  
 ---------------- ----------------------------------------------------------------------------------------------------------------------- 
  Service ID       debug.App\Security\LoginFormAuthenticator                                                                              
  Class            Symfony\Component\Security\Http\Authenticator\Debug\TraceableAuthenticator                                             
  Tags             container.decorator (id: App\Security\LoginFormAuthenticator, inner: debug.App\Security\LoginFormAuthenticator.inner)  
  Public           no                                                                                                                     
  Synthetic        no                                                                                                                     
  Lazy             no                                                                                                                     
  Shared           yes                                                                                                                    
  Abstract         no                                                                                                                     
  Autowired        no                                                                                                                     
  Autoconfigured   no                                                                                                                     
  Usages           App\Security\LoginFormAuthenticator                                                                                    
                   security.command.debug_firewall                                                                                        
                   security.authenticator.manager.main                                                                                    
                   security.exception_listener.main                                                                                       
                   .service_locator.vgziCoP                                                                                               
                   .service_locator.j9y68NC                                                                                               
 ---------------- ----------------------------------------------------------------------------------------------------------------------- 

 ! [NOTE] The "App\Security\LoginFormAuthenticator" service or alias has been removed or inlined when the container was 
 !        compiled.          

Switching to the prod environment seems to "fix" the problem.

How to reproduce

Create a new class App\Security\LoginFormAuthenticator extending Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator. Define the abstract methods and configure it in your configurations:

# config/packages/security.yaml

security:
    firewalls:
        main:
            custom_authenticators:
                - App\Security\LoginFormAuthenticator

Now, create a new class App\Controller\SecurityController with an method register and try to autowire the newly created authenticator via a method parameter:

class SecurityController extends AbstractController
{
    #[Route(path: '/register')]
    public function register(
        App\Security\LoginFormAuthenticator $customAuthenticator,
    ): Response {
        # ...
    }
}

Accessing the route (https://127.0.0.1:8000/register) throws the described exception.

Possible Solution

No response

Additional Context

No response

@amilaev
Copy link

amilaev commented Dec 6, 2024

Same for me. However, the error disappears if I remove the custom_authenticators option.

@Marv51
Copy link

Marv51 commented Dec 10, 2024

I have the same issue when upgrading to 7.2:

I changed my controller to this:

#[Route("/something", methods:["GET", "POST"])]
    public function doSomethingAction(
    #[Autowire(service: 'App\Security\AppCustomAuthentiatorAuthenticator')]
    AuthenticatorInterface $appCustomAuthentiatorAuthenticator)
{
// ....

And that seems to work?

@yblatti
Copy link

yblatti commented Dec 12, 2024

Had the same problem.

To me, it looks like all authenticators are wrapped/decorated now (06f7876#diff-5eece14c3eaf711243f93091c28eb0a6e3dc4e0af44830f87ee0cb436dd39460R644).

In my code I autowire my CustomLoginFormAuthenticator by its class. In 7.2 the decorated service type won't match.

I changed it to AuthenticatorInterface, and specified my CustomLoginFormAuthenticator class as an argument in services.yaml or with the Autowire attribute (thanks @Marv51 !).

Before :

class ProgrammaticUserAuthenticator
{
    public function __construct(private UserAuthenticatorInterface $authenticator, private CustomLoginFormAuthenticator $customFormAuthenticator, private TokenStorageInterface $tokenStorage)

After (services.yaml) :

class ProgrammaticUserAuthenticator
{
    public function __construct(private UserAuthenticatorInterface $authenticator, private AuthenticatorInterface $customFormAuthenticator, private TokenStorageInterface $tokenStorage)
services:
    App\User\Security\ProgrammaticUserAuthenticator:
        class: App\User\Security\ProgrammaticUserAuthenticator
        arguments:
            - '@security.authenticator.manager.main'
            - '@App\User\Security\Authenticator\CustomLoginFormAuthenticator'
            - '@security.token_storage'

After (Autowire attribute) :

class ProgrammaticUserAuthenticator
{
    public function __construct(private UserAuthenticatorInterface $authenticator, #[Autowire(service: 'App\User\Security\Authenticator\CustomLoginFormAuthenticator')] private AuthenticatorInterface $customFormAuthenticator, private TokenStorageInterface $tokenStorage)

Seems to work :)

@chalasr
Copy link
Member

chalasr commented Dec 12, 2024

Although we should certainly try to fix this regression, using the interface as type and wiring the concrete authenticator’s service id explicitly e.g. using #[Autowire] is the way to go. An even better path is to not inject the authenticator at all and rely on the Security helper instead.

@MatTheCat
Copy link
Contributor

@ebitkov #59278 should fix your issue; could you check it works please? 🙏

fabpot added a commit that referenced this issue Dec 30, 2024
…their traceable version (MatTheCat)

This PR was squashed before being merged into the 7.2 branch.

Discussion
----------

[SecurityBundle] Do not replace authenticators service by their traceable version

| Q             | A
| ------------- | ---
| Branch?       | 7.2
| Bug fix?      | yes
| New feature?  | no
| Deprecations? | no
| Issues        | Fix #59071, fix #59091
| License       | MIT

Commits
-------

d44b7af [SecurityBundle] Do not replace authenticators service by their traceable version
@fabpot fabpot closed this as completed Dec 30, 2024
@ebitkov
Copy link
Author

ebitkov commented Jan 15, 2025

@ebitkov #59278 should fix your issue; could you check it works please? 🙏

Even tho my reply is not need here anymore, just for the sake of completeness: Yes, it works. Thank you all <3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants