Requirements

A Symfony > 5.4 project is required to use this bundle, with a Doctrine database configured.

Installation

$ composer require softspring/user-bundle:^5.2

Configuration

Enable the bundle

Add the bundle (and the required bundles) to your config/bundles.php:

<?php

return [
    ...
    Softspring\PermissionsBundle\SfsPermissionsBundle::class => ['all' => true],
    Softspring\TwigExtraBundle\SfsTwigExtraBundle::class => ['all' => true],
    Softspring\UserBundle\SfsUserBundle::class => ['all' => true],
];

Create user class

Create a new User class in src/Entity/User.php:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Softspring\UserBundle\Entity\ConfirmableTrait;
use Softspring\UserBundle\Entity\NameSurnameTrait;
use Softspring\UserBundle\Entity\PasswordRequestTrait;
use Softspring\UserBundle\Entity\RolesAdminTrait;
use Softspring\UserBundle\Entity\UserHasLocalePreferenceTrait;
use Softspring\UserBundle\Entity\UserIdentifierEmailTrait;
use Softspring\UserBundle\Entity\UserLastLoginTrait;
use Softspring\UserBundle\Entity\UserPasswordTrait;
use Softspring\UserBundle\Model\ConfirmableInterface;
use Softspring\UserBundle\Model\NameSurnameInterface;
use Softspring\UserBundle\Model\PasswordRequestInterface;
use Softspring\UserBundle\Model\RolesAdminInterface;
use Softspring\UserBundle\Model\User as UserModel;
use Softspring\UserBundle\Model\UserAvatarInterface;
use Softspring\UserBundle\Model\UserAvatarTrait;
use Softspring\UserBundle\Model\UserHasLocalePreferenceInterface;
use Softspring\UserBundle\Model\UserIdentifierEmailInterface;
use Softspring\UserBundle\Model\UserLastLoginInterface;
use Softspring\UserBundle\Model\UserPasswordInterface;

#[ORM\Entity]
#[ORM\HasLifecycleCallbacks]
class User extends UserModel implements NameSurnameInterface, UserPasswordInterface, PasswordRequestInterface, UserIdentifierEmailInterface, UserAvatarInterface, ConfirmableInterface, RolesAdminInterface, UserLastLoginInterface, UserHasLocalePreferenceInterface
{
    use UserIdentifierEmailTrait;
    use NameSurnameTrait;
    use UserPasswordTrait;
    use PasswordRequestTrait;
    use UserAvatarTrait;
    use ConfirmableTrait;
    use RolesAdminTrait;
    use UserLastLoginTrait;
    use UserHasLocalePreferenceTrait;

    #[ORM\Id]
    #[ORM\Column(length: 13, options: ['fixed' => true])]
    #[ORM\GeneratedValue(strategy: 'NONE')]
    protected ?string $id = null;

    public function getId(): ?string
    {
        return $this->id;
    }

    #[ORM\PrePersist]
    public function _generateId(): void
    {
        $this->id = uniqid();
    }

    public function getDisplayName(): string
    {
        return $this->getName() . ' ' . $this->getSurname();
    }
}

Database migration

$ php bin/console doctrine:migrations:diff --namespace="DoctrineMigrations"
$ php bin/console doctrine:migrations:migrate -n

Configure routes

Configure routes in config/routes/sfs_user.yaml:

# config/routes/sfs_user.yaml
_sfs_login:
    resource: '@SfsUserBundle/config/routing/login.yaml'

# _sfs_invitation:
#     resource: '@SfsUserBundle/config/routing/invitation.yaml'
#     prefix: "/invitation"

_sfs_reset_password:
    resource: '@SfsUserBundle/config/routing/reset_password.yaml'
    prefix: "/reset-password"

_sfs_change_password:
    resource: '@SfsUserBundle/config/routing/change_password.yaml'
    prefix: "/user/change-password"

#_sfs_register:
#    resource: '@SfsUserBundle/config/routing/register.yaml'
#    prefix: "/register"

_sfs_preferences:
    resource: '@SfsUserBundle/config/routing/preferences.yaml'
    prefix: "/user/preferences"

We recommend to subpath the routes, but it depends on your project.

# config/routes.yaml
_sfs_user:
    resource: 'routes/sfs_user.yaml'
    prefix: /app

Configure security

Configure security in config/packages/security.yaml:

# config/packages/security.yaml
imports:
    - { resource: '@SfsUserBundle/config/security/admin_role_hierarchy.yaml' }

security:
    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
    providers:
        sfs_user:
            id: sfs_user.user_provider

    access_decision_manager:
        strategy: unanimous
        allow_if_all_abstain: false

    role_hierarchy:
        ROLE_ADMIN:
            - ROLE_ADMIN_USERS_RW
            # - ROLE_ADMIN_INVITATION_RO
            # - ROLE_ALLOWED_TO_SWITCH
            - ROLE_ADMIN_ADMINISTRATORS_RW
        ROLE_SUPER_ADMIN:
            - ROLE_ADMIN
            - ROLE_ADMIN_ADMINISTRATORS_PROMOTE_SUPER
            - ROLE_ADMIN_ADMINISTRATORS_DEMOTE_SUPER

    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        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

            # login_throttling:
            #     max_attempts: 5
            #     interval: '1 minute'

            # remember_me:
            #     secret: \"%env(APP_SECRET)%\"
            #     lifetime: 2592000 # 1 month
            #     secure: true
            #     httponly: true
            #     always_remember_me: false

            logout:
                path: sfs_user_logout

            # switch_user:
            #     role: ROLE_ALLOWED_TO_SWITCH
            #     parameter: _switch_user

    access_control:
        - { path: ^/app/login, roles: PUBLIC_ACCESS }
        # - { path: ^/app/invitation, roles: PUBLIC_ACCESS }
        - { path: ^/app/reset-password, roles: PUBLIC_ACCESS }
        - { path: ^/admin, roles: ROLE_ADMIN }

when@test:
    security:
        password_hashers:
            # By default, password hashers are resource intensive and take time. This is
            # important to generate secure password hashes. In tests however, secure hashes
            # are not important, waste resources and increase test times. The following
            # reduces the work factor to the lowest possible values.
            Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
                algorithm: auto
                cost: 4 # Lowest possible value for bcrypt
                time_cost: 3 # Lowest possible value for argon
                memory_cost: 10 # Lowest possible value for argo

Create a user

Create a user with the command:

$ php bin/console sfs:user:create username user@example.com 123456

Promote the user to admin:

$ php bin/console sfs:user:promote user@example.com

Check login

Now you can go to login page at /app/login and login with the user you just created.