aserra
aserra

Reputation: 133

Symfony4 Two authentification methods in security

I want two authentications methods in my application.

One for the entity User, and other (admin) with a plaintext.

Very simple.

Thus, when I configure security.yaml, I specify the providers:

security:
    providers:
        user:
            entity:
                class: App\Entity\User
                property: username
        in_memory: 
            memory: 
                users:
                    admin:
                        password: admin
                        roles: 'ROLE_ADMIN'

    encoders:
        App\Entity\User: bcrypt
        Symfony\Component\Security\Core\User\User: plaintext

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        admin: 
            provider: in_memory
            pattern: ^/admin/
            guard:
                provider: in_memory
            form_login:
                login_path: admin_login
                check_path: admin_login
            logout:
                path: /admin/logout
                target: /
        default:
            provider: user
            anonymous: ~
            guard:
                provider: user
            form_login:
                login_path: login
                check_path: login
                default_target_path: login_redirect
                use_referer: true
            logout:
                path: /logout
                target: /

    access_control:
        - { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin, roles: ROLE_ADMIN }
        - { path: ^/dashboard, roles: ROLE_USER }

And return the error:

In GuardAuthenticationFactory.php line 121:

  Because you have multiple guard configurators, you need to set the "guard.e  
  ntry_point" key to one of your configurators ()     

Then, if I have to set the guard.entry_point, I need do something like this:

admin:
    entry_point: app.form_admin_authenticator
main:
    entry_point: app.form_user_authenticator

And therefore, if I undestard, I need to configure a Authentication Listener like this: https://symfony.com/doc/current/components/security/authentication.html (btw, this particular help page is very ambiguous and incomplete)

Is it necessary? It seems too complex for my purpose

Upvotes: 1

Views: 5767

Answers (1)

Tim Rourke
Tim Rourke

Reputation: 11

I ran into this particular error. My situation might be a little different, but I had a similar need to authenticate using different authentication strategies depending on the entry point to the application.

One thing your config doesn't include is a reference to any Guard Authenticator objects. See this documentation for an intro to what role those objects play, and how to define them. Symfony's Security package is pretty complicated, and I found that using Guard Authenticators made the process a lot simpler for my use case.

Here is an example of a security.yaml config referencing two different authenticators. The entry_point configuration tells Symfony which one to try first, because in my particular case, Symfony otherwise wouldn't know which authentication strategy to apply first to an incoming request.

security:
    providers:
        user:
            id: App\My\UserProviderClass

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: ~
            logout:
                path: app_logout

            guard:
                entry_point: App\My\MainAuthenticator
                authenticators:
                    - App\My\MainAuthenticator
                    - App\My\OtherAuthenticator

Custom Guard Authenticators contain a method called supports. This method takes the incoming request as its only argument, and returns true or false based on whether the given authenticator should be applied to the incoming request. A common practice might be to check the request's Symfony route name (as defined by the controller) or perhaps something like the full URI for the request. For example:

    /**
     * Does the authenticator support the given Request?
     *
     * If this returns false, the authenticator will be skipped.
     *
     * @param Request $request
     *
     * @return bool
     */
    public function supports(Request $request): bool
    {
        $matchesMyRoute = 'some_route_name' ===
            $request->attributes->get('_route');

        $matchesMyUri = '/path/to/secured/resource' ===
            $request->getUri();

        return $matchesMyRoute || $matchesMyUri;
    }

You can imagine that if multiple Guard Authenticators exist in the same application, it's likely the case that one would only want them to apply to a request of a certain type, whether the differentiation is based on the kind of auth applied (eg. a header with an API key vs. a stateful session cookie), whether the difference is more about the specific route being hit, or perhaps a combination of factors.

In this case, telling Symfony which Guard Authenticator to try first may be important for your security strategy, or may have performance ramifications. For example, if you had two authenticators, and one had to hit the database to verify a stateful session, but another could verify the request's authentication statelessly, eg. by verifying a JWT's signature, you'd probably want to make the JWT authenticator run first, because it might not need to make a round trip to the database to authenticate the request.

See this article for a deeper explanation: https://symfonycasts.com/screencast/symfony-security/entry-point

Upvotes: 1

Related Questions