Skip to content

User authentication

Authenticate user with multiple user providers

Symfony provides native support for multiple user providers. This makes it easier to integrate any kind of login handlers, including SSO and existing third party bundles (for example, FR3DLdapBundleHWIOauthBundleFOSUserBundle, or BeSimpleSsoAuthBundle).

However, to be able to use external user providers with Ibexa DXP, a valid Ibexa user needs to be injected into the repository. This is mainly for the kernel to be able to manage content-related permissions (but not limited to this).

Depending on your context, you either want to create and return an Ibexa user, or return an existing user, even a generic one.

Whenever a user is matched, Symfony initiates a SecurityEvents::INTERACTIVE_LOGIN event. Every service listening to this event receives an InteractiveLoginEvent object which contains the original security token (that holds the matched user) and the request.

Then, it's up to a listener to retrieve an Ibexa user from the repository and to assign it back to the event object. This user is injected into the repository and used for the rest of the request.

User mapping example

The following example uses the memory user provider, maps memory user to Ibexa repository user, and chains with the Ibexa user provider to be able to use both:

Create as src/EventSubscriber/InteractiveLoginSubscriber.php a subscriber listening to the SecurityEvents::INTERACTIVE_LOGIN event and mapping when needed an in-memory authenticated user to an Ibexa user:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php declare(strict_types=1);

namespace App\EventSubscriber;

use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Core\MVC\Symfony\Security\User;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;

class InteractiveLoginSubscriber implements EventSubscriberInterface
{
    /** @param array<string, string> $userMap */
    public function __construct(
        private readonly UserService $userService,
        private readonly array $userMap = [],
    ) {
    }

    public static function getSubscribedEvents()
    {
        return [
            SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
        ];
    }

    public function onInteractiveLogin(InteractiveLoginEvent $event): void
    {
        $tokenUser = $event->getAuthenticationToken()->getUser();
        if ($tokenUser instanceof InMemoryUser) {
            $userLogin = $this->userMap[$event->getAuthenticationToken()->getUserIdentifier()] ?? 'anonymous';
            $ibexaUser = $this->userService->loadUserByLogin($userLogin);
            $event->getAuthenticationToken()->setUser(new User($ibexaUser));
        }
    }
}

In config/packages/security.yaml, add the memory and chain user providers, store some in-memory users with their passwords in plain text and a basic role, set a plaintext password encoder for the memory provider's InMemoryUser, and configure the firewall to use the chain provider:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
security:
    password_hashers:
        # The in-memory provider requires an encoder
        Symfony\Component\Security\Core\User\InMemoryUser: plaintext
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'

    # https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
    providers:
        in_memory:
            memory:
                users:
                    from_memory_user: { password: from_memory_pass, roles: [ 'ROLE_USER' ] }
                    from_memory_admin: { password: from_memory_publish, roles: [ 'ROLE_USER' ] }
        ibexa:
            id: ibexa.security.user_provider
        # Chaining in_memory and ibexa user providers
        chained:
            chain:
                providers: [ in_memory, ibexa ]

    firewalls:
        # …
        ibexa_front:
            pattern: ^/
            provider: chained
            user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker
            context: ibexa
            form_login:
                enable_csrf: true
                login_path: login
                check_path: login_check
            custom_authenticators:
                - Ibexa\PageBuilder\Security\EditorialMode\FragmentAuthenticator
            entry_point: form_login
            logout:
                path: logout

In the config/services.yaml file, declare the subscriber as a service to pass your user map (it's automatically tagged kernel.event_subscriber as implementing the EventSubscriberInterface, the user service injection is auto-wired):

1
2
3
4
5
App\EventSubscriber\InteractiveLoginSubscriber:
    arguments:
        $userMap:
            from_memory_user: customer
            from_memory_admin: admin