- Documentation >
- Users >
- User authentication >
- User authentication
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, FR3DLdapBundle, HWIOauthBundle, FOSUserBundle, 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 an AuthenticationTokenCreatedEvent event during authentication.
Every service listening to this event receives an object which contains the original security token (that holds the matched user) and a passport.
Then, it's up to a listener to retrieve an Ibexa user from the repository.
This Ibexa user can be
- embedded into
Ibexa\Core\MVC\Symfony\Security\User while forgetting about the original user
- wrapped into
Ibexa\Core\MVC\Symfony\Security\UserWrapped with the original user if needed
Finally, this user is assigned back into the event's token 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/AuthenticationTokenCreatedSubscriber.php subscribing to the AuthenticationTokenCreatedEvent 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
38
39
40
41
42
43
44
45
46
47
48 | <?php declare(strict_types=1);
namespace App\EventSubscriber;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Core\SiteAccess\ConfigResolverInterface;
//use Ibexa\Core\MVC\Symfony\Security\User;
use Ibexa\Core\MVC\Symfony\Security\UserWrapped;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\User\InMemoryUser;
use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent;
final readonly class AuthenticationTokenCreatedSubscriber implements EventSubscriberInterface
{
/** @param array<string, string> $userMap */
public function __construct(
private readonly ConfigResolverInterface $configResolver,
private readonly UserService $userService,
private readonly array $userMap = [],
) {
}
public static function getSubscribedEvents(): array
{
return [
AuthenticationTokenCreatedEvent::class => ['onAuthenticationTokenCreated', 10],
];
}
public function onAuthenticationTokenCreated(AuthenticationTokenCreatedEvent $event): void
{
$tokenUser = $event->getAuthenticatedToken()->getUser();
if (!$tokenUser instanceof InMemoryUser) {
return;
}
$userIdentifier = $event->getAuthenticatedToken()->getUserIdentifier();
$ibexaUser = null;
if (array_key_exists($userIdentifier, $this->userMap)) {
$ibexaUser = $this->userService->loadUserByLogin($this->userMap[$userIdentifier]);
}
if (null === $ibexaUser) {
$anonymousUserId = (int)$this->configResolver->getParameter('anonymous_user_id');
$ibexaUser = $this->userService->loadUser($anonymousUserId);
}
//$event->getAuthenticatedToken()->setUser(new User($ibexaUser));
$event->getAuthenticatedToken()->setUser(new UserWrapped($tokenUser, $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
37 | 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' ] } # Mapped to `generic_customer` user
from_memory_forgotten: { password: from_memory_anonym, roles: [ 'ROLE_USER' ] } # Not mapped so `anonymous` user is loaded
from_memory_admin: { password: from_memory_publish, roles: [ 'ROLE_USER' ] } # Mapped to `admin` 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 config resolver and user service injections are auto-wired):
| services:
App\EventSubscriber\AuthenticationTokenCreatedSubscriber:
arguments:
$userMap:
from_memory_user: generic_customer
from_memory_admin: admin
|
From the back office, create the mapped users.
For the example, a new user with the login generic_customer and a random password for the mapping to work,
this account can be in the Customers or the Anonymous users group.
You can now log in with a in-memory user.
In the Symfony debug toolbar, you should see the in-memory user as this example uses UserWrapped.