User Bundle Login And Security
This guide focuses on the runtime security integration of the bundle.
The login page itself is only one part. A working setup also needs:
- the provider
- the firewall
- public access for the login and recovery routes
- optional remember me and login throttling
- optional switch user and impersonation bar
Login Routes
Import the login routes:
YAML
_sfs_login:
resource: '@SfsUserBundle/config/routing/login.yaml'
PHP
<?php
// config/routes.php
namespace Symfony\Component\Routing\Loader\Configurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('@SfsUserBundle/config/routing/login.yaml');
};
This exposes:
sfs_user_loginsfs_user_login_checksfs_user_logout
The check and logout controllers are placeholders. They must be handled by Symfony Security through your firewall configuration.
Provider And Firewall
Use the bundle provider:
YAML
security:
providers:
sfs_user:
id: sfs_user.user_provider
PHP
<?php
// config/routing/login.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', ['providers' => ['sfs_user' => ['id' => 'sfs_user.user_provider']]]);
};
Then connect it to your main firewall:
YAML
security:
firewalls:
main:
pattern: ^/(admin|app)/
lazy: true
provider: sfs_user
form_login:
provider: sfs_user
login_path: sfs_user_login
check_path: sfs_user_login_check
logout:
path: sfs_user_logout
PHP
<?php
// config/routing/login.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', [
'firewalls' => [
'main' => [
'pattern' => '^/(admin|app)/',
'lazy' => true,
'provider' => 'sfs_user',
'form_login' => [
'provider' => 'sfs_user',
'login_path' => 'sfs_user_login',
'check_path' => 'sfs_user_login_check',
],
'logout' => ['path' => 'sfs_user_logout'],
],
],
]);
};
Without this, the login form can render but authentication will not happen.
Public Access Rules
The login and recovery pages are public routes.
A practical access control baseline is:
YAML
security:
access_control:
- { path: ^/app/login, roles: PUBLIC_ACCESS }
- { path: ^/app/reset-password, roles: PUBLIC_ACCESS }
- { path: ^/app/register, roles: PUBLIC_ACCESS }
- { path: ^/app/invitation, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
PHP
<?php
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', [
'access_control' => [
['path' => '^/app/login', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/app/reset-password', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/app/register', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/app/invitation', 'roles' => 'PUBLIC_ACCESS'],
['path' => '^/admin', 'roles' => 'ROLE_ADMIN'],
],
]);
};
Only keep the public entries for features you really use.
Redirect After Login
The bundle supports a configurable target path parameter:
YAML
sfs_user:
login:
target_path_parameter: _target_path
PHP
<?php
// config/packages/sfs_user.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('sfs_user', ['login' => ['target_path_parameter' => '_target_path']]);
};
When this option is enabled, login and register pages preserve that parameter and forward it to the security check path.
Use this when:
- users are redirected to login from a protected page
- the application has several post-login landing pages
- you want custom onboarding flows after authentication
Login Throttling
The bundle does not implement throttling by itself. It relies on the standard Symfony feature.
Example:
YAML
security:
firewalls:
main:
login_throttling:
max_attempts: 5
interval: '1 minute'
PHP
<?php
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', ['firewalls' => ['main' => ['login_throttling' => ['max_attempts' => 5, 'interval' => '1 minute']]]]);
};
Install symfony/rate-limiter if the project does not already include it. Keep this configuration inside the same firewall that handles the login check.
Remember Me
Remember me is also standard Symfony configuration:
YAML
security:
firewalls:
main:
remember_me:
secret: '%env(APP_SECRET)%'
lifetime: 2592000
secure: true
httponly: true
always_remember_me: false
PHP
<?php
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', [
'firewalls' => [
'main' => [
'remember_me' => [
'secret' => '%env(APP_SECRET)%',
'lifetime' => 2592000,
'secure' => true,
'httponly' => true,
'always_remember_me' => false,
],
],
],
]);
};
The example sends the remember-me cookie only over HTTPS. For local HTTP development, configure the cookie policy separately. The login form must submit _remember_me when always_remember_me is false.
For admin-only backoffices, many teams keep it disabled.
Switch User
The bundle supports Symfony switch_user and adds extra authorization checks through SwitchUserVoter.
Base config:
YAML
security:
firewalls:
main:
switch_user:
role: ROLE_ALLOWED_TO_SWITCH
parameter: _switch_user
PHP
<?php
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', [
'firewalls' => [
'main' => ['switch_user' => ['role' => 'ROLE_ALLOWED_TO_SWITCH', 'parameter' => '_switch_user']],
],
]);
};
Typical role setup:
YAML
security:
role_hierarchy:
ROLE_ADMIN:
- ROLE_ALLOWED_TO_SWITCH
PHP
<?php
// config/packages/security.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('security', ['role_hierarchy' => ['ROLE_ADMIN' => ['ROLE_ALLOWED_TO_SWITCH']]]);
};
The voter adds extra restrictions:
- super admins cannot be impersonated
- admin-to-admin impersonation is restricted
These vetoes depend on the access-decision strategy. Use the unanimous strategy from the installation guide so a role grant does not override a subject-specific denial. The voter recognizes ROLE_ALLOWED_TO_SWITCH; keep that role when using this implementation.
Impersonation Bar
The bundle can inject a visible bar while an admin is impersonating another user.
Example:
YAML
sfs_user:
impersonate_bar:
enabled: true
switch_role: ROLE_ALLOWED_TO_SWITCH
switch_route: app_entrypoint
switch_parameter: _switch_user
PHP
<?php
// config/packages/sfs_user.php
namespace Symfony\Component\DependencyInjection\Loader\Configurator;
return static function (ContainerConfigurator $container): void {
$container->extension('sfs_user', [
'impersonate_bar' => [
'enabled' => true,
'switch_role' => 'ROLE_ALLOWED_TO_SWITCH',
'switch_route' => 'app_entrypoint',
'switch_parameter' => '_switch_user',
],
]);
};
This is especially useful in support or customer success tools, because it makes impersonation obvious and gives a quick exit path.
In armonic-standalone, only switch_route is configured and the bar can be enabled when the project needs it.
User Identifiers
The provider resolves the user from your concrete entity class.
The actual identifier depends on the interfaces your entity implements:
UserIdentifierEmailInterfacefor email loginUserIdentifierUsernameInterfacefor username login
Choose one early. Most modern projects use email as the main identifier.
Troubleshooting
- The login form renders but authentication never happens:
Checkform_login.check_pathandprovider. - Logout throws the placeholder exception:
Check thatlogout.pathis configured on the firewall. - Protected pages keep redirecting to login:
Check firewall pattern andaccess_control. - Switch user works too broadly:
Review your role hierarchy and the subject voter behaviour.
Related Guides
- Install for the base entity, route imports, and first security baseline.
- Register and reset password to add public onboarding and recovery flows.
- Admin area if the same firewall must also cover the admin backoffice.
- OAuth login when standard login is not enough and the project needs external authentication.