From 96a7f2fad995a5ca3aec8d6dd359b7d9fdd3b1e1 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:20:32 +0100 Subject: [PATCH 01/12] =?UTF-8?q?user=5Fauthentication.md:=20fixes,=20enco?= =?UTF-8?q?ders=20=E2=86=92=20password=5Fhashers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/users/user_authentication.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/users/user_authentication.md b/docs/users/user_authentication.md index d2b7280301..e534b230a2 100644 --- a/docs/users/user_authentication.md +++ b/docs/users/user_authentication.md @@ -52,9 +52,15 @@ security: users: # You will then be able to login with username "user" and password "userpass" user: { password: userpass, roles: [ 'ROLE_USER' ] } - # The "in memory" provider requires an encoder for Symfony\Component\Security\Core\User\User - encoders: - Symfony\Component\Security\Core\User\User: plaintext + password_hashers: + # The "in memory" provider requires an encoder + Symfony\Component\Security\Core\User\InMemoryUser: plaintext + Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' + firewalls: + ibexa_front: + pattern: ^/ + provider: chain_provider + # … ``` ### Implement the listener From f2d32a3d2b721a0ade43da3cc5ad5f7dfea22c18 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 08:39:49 +0100 Subject: [PATCH 02/12] user_authentication.md: fix subscriber --- docs/users/user_authentication.md | 40 +++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/users/user_authentication.md b/docs/users/user_authentication.md index e534b230a2..174a1aad3d 100644 --- a/docs/users/user_authentication.md +++ b/docs/users/user_authentication.md @@ -50,8 +50,8 @@ security: in_memory: memory: users: - # You will then be able to login with username "user" and password "userpass" - user: { password: userpass, roles: [ 'ROLE_USER' ] } + # You will then be able to log in with username "from_memory_user" and password "from_memory_pass" + from_memory_user: { password: from_memory_pass, roles: [ 'ROLE_USER' ] } password_hashers: # The "in memory" provider requires an encoder Symfony\Component\Security\Core\User\InMemoryUser: plaintext @@ -69,10 +69,10 @@ In the `config/services.yaml` file: ``` yaml services: - App\EventListener\InteractiveLoginListener: + App\EventSubscriber\InteractiveLoginSubscriber: arguments: ['@ibexa.api.service.user'] tags: - - { name: kernel.event_subscriber }  + - { name: kernel.event_subscriber } ``` Don't mix `MVCEvents::INTERACTIVE_LOGIN` event (specific to [[= product_name =]]) and `SecurityEvents::INTERACTIVE_LOGIN` event (fired by Symfony security component). @@ -80,37 +80,37 @@ Don't mix `MVCEvents::INTERACTIVE_LOGIN` event (specific to [[= product_name =]] ``` php userService = $userService; } public static function getSubscribedEvents() { return [ - MVCEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin' + SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin' ]; } public function onInteractiveLogin(InteractiveLoginEvent $event) { - // This loads a generic User and assigns it back to the event. - // You may want to create Users here, or even load predefined Users depending on your own rules. - $event->setApiUser($this->userService->loadUserByLogin( 'lolautruche' )); + $userMap = [ + 'from_memory_user' => 'generic_customer_account', + ]; + $userLogin = $userMap[$event->getAuthenticationToken()->getUserIdentifier()] ?? 'anonymous'; + $ibexaUser = $this->userService->loadUserByLogin($userLogin); + $event->getAuthenticationToken()->setUser(new User($ibexaUser)); + + return $event; } -}  +} ``` From d6d7933edfdda3015e61c995e42a47d66dfcd582 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:31:09 +0100 Subject: [PATCH 03/12] user_authentication.md: move code to external files --- .../in_memory/config/packages/security.yaml | 36 ++++++++++ .../in_memory/config/services.yaml | 5 ++ .../InteractiveLoginSubscriber.php | 35 ++++++++++ docs/users/user_authentication.md | 65 +------------------ 4 files changed, 79 insertions(+), 62 deletions(-) create mode 100644 code_samples/user_management/in_memory/config/packages/security.yaml create mode 100644 code_samples/user_management/in_memory/config/services.yaml create mode 100644 code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php diff --git a/code_samples/user_management/in_memory/config/packages/security.yaml b/code_samples/user_management/in_memory/config/packages/security.yaml new file mode 100644 index 0000000000..42fafb542b --- /dev/null +++ b/code_samples/user_management/in_memory/config/packages/security.yaml @@ -0,0 +1,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: + ibexa: + id: ibexa.security.user_provider + in_memory: + memory: + users: + # You will then be able to log in with username "from_memory_user" and password "from_memory_pass" + from_memory_user: { password: from_memory_pass, roles: [ 'ROLE_USER' ] } + # Chaining in_memory and ibexa user providers + chain_provider: + chain: + providers: [ in_memory, ibexa ] + + firewalls: + # … + ibexa_front: + pattern: ^/ + provider: chain_provider + 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 diff --git a/code_samples/user_management/in_memory/config/services.yaml b/code_samples/user_management/in_memory/config/services.yaml new file mode 100644 index 0000000000..5deab6f8cb --- /dev/null +++ b/code_samples/user_management/in_memory/config/services.yaml @@ -0,0 +1,5 @@ +services: + App\EventSubscriber\InteractiveLoginSubscriber: + arguments: ['@ibexa.api.service.user'] + tags: + - { name: kernel.event_subscriber } diff --git a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php new file mode 100644 index 0000000000..b5128ead24 --- /dev/null +++ b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php @@ -0,0 +1,35 @@ + 'onInteractiveLogin' + ]; + } + + public function onInteractiveLogin(InteractiveLoginEvent $event) + { + $userMap = [ + 'from_memory_user' => 'generic_customer_account', + ]; + $userLogin = $userMap[$event->getAuthenticationToken()->getUserIdentifier()] ?? 'anonymous'; + $ibexaUser = $this->userService->loadUserByLogin($userLogin); + $event->getAuthenticationToken()->setUser(new User($ibexaUser)); + + return $event; + } +} diff --git a/docs/users/user_authentication.md b/docs/users/user_authentication.md index 174a1aad3d..d3f63d0687 100644 --- a/docs/users/user_authentication.md +++ b/docs/users/user_authentication.md @@ -39,28 +39,7 @@ The following is an example of using the in-memory user provider: ``` yaml # config/packages/security.yaml -security: - providers: - # Chaining in_memory and ibexa user providers - chain_provider: - chain: - providers: [in_memory, ibexa] - ibexa: - id: ibexa.security.user_provider - in_memory: - memory: - users: - # You will then be able to log in with username "from_memory_user" and password "from_memory_pass" - from_memory_user: { password: from_memory_pass, roles: [ 'ROLE_USER' ] } - password_hashers: - # The "in memory" provider requires an encoder - Symfony\Component\Security\Core\User\InMemoryUser: plaintext - Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' - firewalls: - ibexa_front: - pattern: ^/ - provider: chain_provider - # … +[[= include_file('code_samples/user_management/in_memory/config/packages/security.yaml') =]] ``` ### Implement the listener @@ -68,49 +47,11 @@ security: In the `config/services.yaml` file: ``` yaml -services: - App\EventSubscriber\InteractiveLoginSubscriber: - arguments: ['@ibexa.api.service.user'] - tags: - - { name: kernel.event_subscriber } +[[= include_file('code_samples/user_management/in_memory/config/services.yaml') =]] ``` Don't mix `MVCEvents::INTERACTIVE_LOGIN` event (specific to [[= product_name =]]) and `SecurityEvents::INTERACTIVE_LOGIN` event (fired by Symfony security component). ``` php - 'onInteractiveLogin' - ]; - } - - public function onInteractiveLogin(InteractiveLoginEvent $event) - { - $userMap = [ - 'from_memory_user' => 'generic_customer_account', - ]; - $userLogin = $userMap[$event->getAuthenticationToken()->getUserIdentifier()] ?? 'anonymous'; - $ibexaUser = $this->userService->loadUserByLogin($userLogin); - $event->getAuthenticationToken()->setUser(new User($ibexaUser)); - - return $event; - } -} +[[= include_file('code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php') =]] ``` From 8e2f61961b313be7dc3803ee33c308c1572648b4 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:35:35 +0100 Subject: [PATCH 04/12] user_authentication.md: start to update explanations --- docs/users/user_authentication.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/users/user_authentication.md b/docs/users/user_authentication.md index d3f63d0687..0fa79f9cf4 100644 --- a/docs/users/user_authentication.md +++ b/docs/users/user_authentication.md @@ -6,25 +6,26 @@ description: Customize user authentication. ## Authenticate user with multiple user providers -Symfony provides native support for [multiple user providers]([[= symfony_doc =]]/security/user_providers.html). +Symfony provides native support for [multiple user providers]([[= symfony_doc =]]/security/user_providers.html). This makes it easier to integrate any kind of login handlers, including SSO and existing third party bundles (for example, [FR3DLdapBundle](https://github.com/Maks3w/FR3DLdapBundle), [HWIOauthBundle](https://github.com/hwi/HWIOAuthBundle), [FOSUserBundle](https://github.com/FriendsOfSymfony/FOSUserBundle), or [BeSimpleSsoAuthBundle](https://github.com/BeSimple/BeSimpleSsoAuthBundle)). -However, to be able to use *external* user providers with [[= product_name =]], a valid Platform user needs to be injected into the repository. +However, to be able to use *external* user providers with [[= product_name =]], 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 a Platform user, return an existing user, or even always use a generic user. +Depending on your context, you either want to create and return an Ibexa user, or return an existing user, even a generic one. -Whenever an *external* user is matched (i.e. one that doesn't come from Platform repository, like coming from LDAP), [[= product_name =]] kernel initiates an `MVCEvents::INTERACTIVE_LOGIN` event. -Every service listening to this event receives an `Ibexa\Core\MVC\Symfony\Event\InteractiveLoginEvent` object which contains the original security token (that holds the matched user) and the request. +Whenever an *external* user is matched (i.e. one that doesn't come from Platform repository, like coming from LDAP), [[= product_name =]] kernel initiates a `SecurityEvents::INTERACTIVE_LOGIN` event. +Every service listening to this event receives an `Symfony\Component\Security\Http\Event\InteractiveLoginEvent` object which contains the original security token (that holds the matched user) and the request. -Then, it's up to the listener to retrieve a Platform user from the repository and to assign it back to the event object. +Then, it's up to the 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. -If no [[= product_name =]] user is returned, the Anonymous user is used. +If no [[= product_name =]] user is returned, the Anonymous user is used. TODO: check this statement. ### User exposed and security token -When an *external* user is matched, a different token is injected into the security context, the `InteractiveLoginToken`. +When an *external* user is matched, a different token is injected into the security context, the `InteractiveLoginToken`. +TODO: There is no such token class This token holds a `UserWrapped` instance which contains the originally matched user and the *API user* (the one from the [[= product_name =]] repository). The *API user* is mainly used for permission checks against the repository and thus stays *under the hood*. @@ -32,6 +33,7 @@ The *API user* is mainly used for permission checks against the repository and ### Customize the user class It's possible to customize the user class used by extending `Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener` service, which defaults to `Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener`. +TODO: There is no such class You can override `getUser()` to return whatever user class you want, as long as it implements `Ibexa\Core\MVC\Symfony\Security\UserInterface`. @@ -42,7 +44,7 @@ The following is an example of using the in-memory user provider: [[= include_file('code_samples/user_management/in_memory/config/packages/security.yaml') =]] ``` -### Implement the listener +### Implement the subscriber In the `config/services.yaml` file: @@ -50,8 +52,6 @@ In the `config/services.yaml` file: [[= include_file('code_samples/user_management/in_memory/config/services.yaml') =]] ``` -Don't mix `MVCEvents::INTERACTIVE_LOGIN` event (specific to [[= product_name =]]) and `SecurityEvents::INTERACTIVE_LOGIN` event (fired by Symfony security component). - ``` php [[= include_file('code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php') =]] ``` From 9f61b095f77acc005038b0aac064e72ec678a66e Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 09:40:15 +0100 Subject: [PATCH 05/12] InteractiveLoginSubscriber::onInteractiveLogin() return type --- .../src/EventSubscriber/InteractiveLoginSubscriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php index b5128ead24..c607a7f50c 100644 --- a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php +++ b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php @@ -21,7 +21,7 @@ public static function getSubscribedEvents() ]; } - public function onInteractiveLogin(InteractiveLoginEvent $event) + public function onInteractiveLogin(InteractiveLoginEvent $event): InteractiveLoginEvent { $userMap = [ 'from_memory_user' => 'generic_customer_account', From 0e6a2a671d2decb38d0cab31ca8889e8b36bae07 Mon Sep 17 00:00:00 2001 From: adriendupuis Date: Fri, 13 Mar 2026 08:47:36 +0000 Subject: [PATCH 06/12] PHP & JS CS Fixes --- .../src/EventSubscriber/InteractiveLoginSubscriber.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php index c607a7f50c..ea894fba0c 100644 --- a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php +++ b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php @@ -4,8 +4,8 @@ use Ibexa\Contracts\Core\Repository\UserService; use Ibexa\Core\MVC\Symfony\Security\User; -use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; class InteractiveLoginSubscriber implements EventSubscriberInterface @@ -17,7 +17,7 @@ public function __construct(private readonly UserService $userService) public static function getSubscribedEvents() { return [ - SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin' + SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin', ]; } From a1cb9a9ec36e1d34488ce74ca87e8d4c26789b42 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:11:57 +0100 Subject: [PATCH 07/12] user_authentication.md: Rewrite example description --- .../in_memory/config/packages/security.yaml | 10 ++--- .../in_memory/config/services.yaml | 10 ++--- .../InteractiveLoginSubscriber.php | 18 +++++--- docs/users/user_authentication.md | 44 ++++++++----------- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/code_samples/user_management/in_memory/config/packages/security.yaml b/code_samples/user_management/in_memory/config/packages/security.yaml index 42fafb542b..a5239f50f1 100644 --- a/code_samples/user_management/in_memory/config/packages/security.yaml +++ b/code_samples/user_management/in_memory/config/packages/security.yaml @@ -6,15 +6,15 @@ security: # https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded providers: - ibexa: - id: ibexa.security.user_provider in_memory: memory: users: - # You will then be able to log in with username "from_memory_user" and password "from_memory_pass" 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 - chain_provider: + chained: chain: providers: [ in_memory, ibexa ] @@ -22,7 +22,7 @@ security: # … ibexa_front: pattern: ^/ - provider: chain_provider + provider: chained user_checker: Ibexa\Core\MVC\Symfony\Security\UserChecker context: ibexa form_login: diff --git a/code_samples/user_management/in_memory/config/services.yaml b/code_samples/user_management/in_memory/config/services.yaml index 5deab6f8cb..921ff2149c 100644 --- a/code_samples/user_management/in_memory/config/services.yaml +++ b/code_samples/user_management/in_memory/config/services.yaml @@ -1,5 +1,5 @@ -services: - App\EventSubscriber\InteractiveLoginSubscriber: - arguments: ['@ibexa.api.service.user'] - tags: - - { name: kernel.event_subscriber } +App\EventSubscriber\InteractiveLoginSubscriber: + arguments: + $userMap: + from_memory_user: customer + from_memory_admin: admin diff --git a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php index ea894fba0c..cb8ebfb1af 100644 --- a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php +++ b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php @@ -5,12 +5,16 @@ 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 { - public function __construct(private readonly UserService $userService) + public function __construct( + private readonly UserService $userService, + private readonly array $userMap = [], + ) { } @@ -23,12 +27,12 @@ public static function getSubscribedEvents() public function onInteractiveLogin(InteractiveLoginEvent $event): InteractiveLoginEvent { - $userMap = [ - 'from_memory_user' => 'generic_customer_account', - ]; - $userLogin = $userMap[$event->getAuthenticationToken()->getUserIdentifier()] ?? 'anonymous'; - $ibexaUser = $this->userService->loadUserByLogin($userLogin); - $event->getAuthenticationToken()->setUser(new User($ibexaUser)); + $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)); + } return $event; } diff --git a/docs/users/user_authentication.md b/docs/users/user_authentication.md index 0fa79f9cf4..37c705f5d3 100644 --- a/docs/users/user_authentication.md +++ b/docs/users/user_authentication.md @@ -14,44 +14,38 @@ This is mainly for the kernel to be able to manage content-related permissions ( Depending on your context, you either want to create and return an Ibexa user, or return an existing user, even a generic one. -Whenever an *external* user is matched (i.e. one that doesn't come from Platform repository, like coming from LDAP), [[= product_name =]] kernel initiates a `SecurityEvents::INTERACTIVE_LOGIN` event. -Every service listening to this event receives an `Symfony\Component\Security\Http\Event\InteractiveLoginEvent` object which contains the original security token (that holds the matched user) and the request. +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 the listener to retrieve an Ibexa user from the repository and to assign it back to the event object. +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. -If no [[= product_name =]] user is returned, the Anonymous user is used. TODO: check this statement. +### User mapping example -### User exposed and security token +The following example uses the [memory user provider]([[= symfony_doc =]]/security/user_providers.html#memory-user-provider), +maps memory user to Ibexa repository user, +and [chains]([[= symfony_doc =]]/security/user_providers.html#chain-user-provider) with the Ibexa user provider to be able to use both: -When an *external* user is matched, a different token is injected into the security context, the `InteractiveLoginToken`. -TODO: There is no such token class -This token holds a `UserWrapped` instance which contains the originally matched user and the *API user* (the one from the [[= product_name =]] repository). +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: -The *API user* is mainly used for permission checks against the repository and thus stays *under the hood*. - -### Customize the user class - -It's possible to customize the user class used by extending `Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener` service, which defaults to `Ibexa\Core\MVC\Symfony\Security\EventListener\SecurityListener`. -TODO: There is no such class - -You can override `getUser()` to return whatever user class you want, as long as it implements `Ibexa\Core\MVC\Symfony\Security\UserInterface`. +``` php +[[= include_file('code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php') =]] +``` -The following is an example of using the in-memory user provider: +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: ``` yaml -# config/packages/security.yaml [[= include_file('code_samples/user_management/in_memory/config/packages/security.yaml') =]] ``` -### Implement the subscriber - -In the `config/services.yaml` file: +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): ``` yaml [[= include_file('code_samples/user_management/in_memory/config/services.yaml') =]] ``` - -``` php -[[= include_file('code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php') =]] -``` From 8b944e2f1922cf4b62ae6f54fd824954ea0f79b3 Mon Sep 17 00:00:00 2001 From: adriendupuis Date: Fri, 13 Mar 2026 11:20:02 +0000 Subject: [PATCH 08/12] PHP & JS CS Fixes --- .../src/EventSubscriber/InteractiveLoginSubscriber.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php index cb8ebfb1af..14c5aa5980 100644 --- a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php +++ b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php @@ -14,8 +14,7 @@ class InteractiveLoginSubscriber implements EventSubscriberInterface public function __construct( private readonly UserService $userService, private readonly array $userMap = [], - ) - { + ) { } public static function getSubscribedEvents() From 0e5516ef5cae2765a766e824aa37bc579b060ccb Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 12:22:35 +0100 Subject: [PATCH 09/12] InteractiveLoginSubscriber: Fix missingType.iterableValue Method App\EventSubscriber\InteractiveLoginSubscriber::__construct() has parameter $userMap with no value type specified in iterable type array. --- .../in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php | 1 + 1 file changed, 1 insertion(+) diff --git a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php index 14c5aa5980..0895ee1a79 100644 --- a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php +++ b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php @@ -11,6 +11,7 @@ class InteractiveLoginSubscriber implements EventSubscriberInterface { + /** @param array $userMap */ public function __construct( private readonly UserService $userService, private readonly array $userMap = [], From aab4c05b951c02598a656d90945d42271b9b5cce Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 15:56:11 +0100 Subject: [PATCH 10/12] InteractiveLoginSubscriber: No need to return the event --- .../src/EventSubscriber/InteractiveLoginSubscriber.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php index 0895ee1a79..ac000c0439 100644 --- a/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php +++ b/code_samples/user_management/in_memory/src/EventSubscriber/InteractiveLoginSubscriber.php @@ -25,7 +25,7 @@ public static function getSubscribedEvents() ]; } - public function onInteractiveLogin(InteractiveLoginEvent $event): InteractiveLoginEvent + public function onInteractiveLogin(InteractiveLoginEvent $event): void { $tokenUser = $event->getAuthenticationToken()->getUser(); if ($tokenUser instanceof InMemoryUser) { @@ -33,7 +33,5 @@ public function onInteractiveLogin(InteractiveLoginEvent $event): InteractiveLog $ibexaUser = $this->userService->loadUserByLogin($userLogin); $event->getAuthenticationToken()->setUser(new User($ibexaUser)); } - - return $event; } } From d846a46c8f594df02dc9ebe2028f9747b3d2a835 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:45:27 +0100 Subject: [PATCH 11/12] services.yaml: Format --- .../user_management/in_memory/config/services.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/code_samples/user_management/in_memory/config/services.yaml b/code_samples/user_management/in_memory/config/services.yaml index 921ff2149c..83124bea6a 100644 --- a/code_samples/user_management/in_memory/config/services.yaml +++ b/code_samples/user_management/in_memory/config/services.yaml @@ -1,5 +1,6 @@ -App\EventSubscriber\InteractiveLoginSubscriber: - arguments: - $userMap: - from_memory_user: customer - from_memory_admin: admin +services: + App\EventSubscriber\InteractiveLoginSubscriber: + arguments: + $userMap: + from_memory_user: customer + from_memory_admin: admin From c6ef3c5c8233b0fbcec94196bfa5fe864b815db4 Mon Sep 17 00:00:00 2001 From: Adrien Dupuis <61695653+adriendupuis@users.noreply.github.com> Date: Fri, 13 Mar 2026 19:00:11 +0100 Subject: [PATCH 12/12] user_authentication.md: closer to reality --- docs/users/user_authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/users/user_authentication.md b/docs/users/user_authentication.md index 37c705f5d3..a15af3fcca 100644 --- a/docs/users/user_authentication.md +++ b/docs/users/user_authentication.md @@ -17,8 +17,8 @@ Depending on your context, you either want to create and return an Ibexa user, o 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. +Then, it's up to a listener to retrieve an Ibexa user from the repository. +This user is wrapped within `Ibexa\Core\MVC\Symfony\Security\User` and assigned back into the event's token for the rest of the request. ### User mapping example