Symfony 4 Multiple guard authenticator

169209
,

Bonjour à tous, j'ai un petit soucis j'aimerai crée deux LoginFormAuthenticator l'un pour les utilisateur front et l'autre pour les utilisateur en Back.
J'ai regarder la doc officiel de symfony mais cependant rien ne marche pour moi, quand je tape /profile je suis toujours rediriger vers le formulaire de login pour l'administrateur.

Voici mon code
Security.yml

security:
    encoders:
        App\Entity\Users:
            algorithm: 'argon2i'
            # maximum memory (in KiB) that may be used to compute the Argon2 hash
            memory_cost: 1024
            #  number of times the Argon2 hash algorithm will be run
            time_cost: 2
            # number of threads to use for computing the Argon2 hash
            threads: 2
    # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
    providers:
        app_user_provider:
            entity:
                class: App\Entity\Users
                property: username
    firewalls:

        main:
            anonymous: true
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
            logout:
                path:   app_logout
            remember_me:
                secret: '%kernel.secret%'
                lifetime: 604800
                path: /
        front:
            anonymous: true
            guard:
                authenticators:
                    - App\Security\FrontLoginFormAuthenticator
            logout:
                path:   app_logout
            remember_me:
                secret: '%kernel.secret%'
                lifetime: 604800
                path: /

            # activate different ways to authenticate

            # http_basic: true
            # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate

            # form_login: true
            # https://symfony.com/doc/current/security/form_login_setup.html
    role_hierarchy:
        ROLE_ADMIN:          ROLE_ADMIN
        ROLE_USER:          [ROLE_USER]


    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
         - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
         - { path: ^/admin, roles: ROLE_ADMIN }
         - { path: ^/profile, roles: ROLE_USER }

LoginFormAuthenticator pour le BACK

<?php

namespace App\Security;

use App\Entity\Users;
use Doctrine\ORM\EntityManagerInterface;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

/**
 * Class LoginFormAuthenticator
 *
 * @package App\Security
 *
 */
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private $entityManager;

    private $router;

    private $csrfTokenManager;

    private $passwordEncoder;

    private $authorizationChecker;

    /**
     * LoginFormAuthenticator constructor.
     *
     * @param EntityManagerInterface        $entityManager
     * @param RouterInterface               $router
     * @param CsrfTokenManagerInterface     $csrfTokenManager
     * @param UserPasswordEncoderInterface  $passwordEncoder
     * @param AuthorizationCheckerInterface $authorizationChecker
     */
    public function __construct(
        EntityManagerInterface $entityManager,
        RouterInterface $router,
        CsrfTokenManagerInterface $csrfTokenManager,
        UserPasswordEncoderInterface $passwordEncoder,
        AuthorizationCheckerInterface $authorizationChecker
    ) {
        $this->entityManager = $entityManager;
        $this->router = $router;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
        $this->authorizationChecker = $authorizationChecker;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(Request $request)
    {
        return 'app_login' === $request->attributes->get('_route')
               && $request->isMethod('POST');
    }

    /**
     * {@inheritdoc}
     */
    public function getCredentials(Request $request)
    {
        $credentials = [
            'email'      => strtolower($request->request->get('email')),
            'password'   => $request->request->get('password'),
            'csrf_token' => $request->request->get('_csrf_token'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['email']
        );

        return $credentials;
    }

    /**
     * {@inheritdoc}
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_token']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(Users::class)->findOneBy(['email' => $credentials['email']]);
        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('error.invalid_user_account');
        }
        // user disabled
        if (!$user->isEnabled()) {
            throw new CustomUserMessageAuthenticationException('error.account_disabled');
        }

        return $user;
    }

    /**
     * {@inheritdoc}
     */
    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    /**
     * {@inheritdoc}
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {

        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        $urlName = $this->authorizationChecker->isGranted(Users::ROLE_ADMIN) ? 'admin_index' : 'index_front';
//            : ($this->authorizationChecker->isGranted(Users::ROLE_CHEF_RESEAUX) ? 'app_login' : '');

        return new RedirectResponse($this->router->generate($urlName));
    }

    /**
     * {@inheritdoc}
     */
    protected function getLoginUrl()
    {
        return $this->router->generate('app_login');
    }
}

LoginFormAuthenticator pour le Front

<?php

namespace App\Security;

use App\Entity\Users;
use Doctrine\ORM\EntityManagerInterface;

use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator;
use Symfony\Component\Security\Http\Util\TargetPathTrait;

/**
 * Class LoginFormAuthenticator
 *
 * @package App\Security
 *
 */
class FrontLoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
    use TargetPathTrait;

    private $entityManager;

    private $router;

    private $csrfTokenManager;

    private $passwordEncoder;

    private $authorizationChecker;

    /**
     * LoginFormAuthenticator constructor.
     *
     * @param EntityManagerInterface        $entityManager
     * @param RouterInterface               $router
     * @param CsrfTokenManagerInterface     $csrfTokenManager
     * @param UserPasswordEncoderInterface  $passwordEncoder
     * @param AuthorizationCheckerInterface $authorizationChecker
     */
    public function __construct(
        EntityManagerInterface $entityManager,
        RouterInterface $router,
        CsrfTokenManagerInterface $csrfTokenManager,
        UserPasswordEncoderInterface $passwordEncoder,
        AuthorizationCheckerInterface $authorizationChecker
    ) {
        $this->entityManager = $entityManager;
        $this->router = $router;
        $this->csrfTokenManager = $csrfTokenManager;
        $this->passwordEncoder = $passwordEncoder;
        $this->authorizationChecker = $authorizationChecker;
    }

    /**
     * {@inheritdoc}
     */
    public function supports(Request $request)
    {
        return 'front_login' === $request->attributes->get('_route')
               && $request->isMethod('POST');
    }

    /**
     * {@inheritdoc}
     */
    public function getCredentials(Request $request)
    {
        $credentials = [
            'email'      => strtolower($request->request->get('email')),
            'password'   => $request->request->get('password'),
            'csrf_tokens' => $request->request->get('_csrf_tokens'),
        ];
        $request->getSession()->set(
            Security::LAST_USERNAME,
            $credentials['email']
        );

        return $credentials;
    }

    /**
     * {@inheritdoc}
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        $token = new CsrfToken('authenticate', $credentials['csrf_tokens']);
        if (!$this->csrfTokenManager->isTokenValid($token)) {
            throw new InvalidCsrfTokenException();
        }

        $user = $this->entityManager->getRepository(Users::class)->findOneBy(['email' => $credentials['email']]);
        if (!$user) {
            // fail authentication with a custom error
            throw new CustomUserMessageAuthenticationException('error.invalid_user_account');
        }
        // user disabled
        if (!$user->isEnabled()) {
            throw new CustomUserMessageAuthenticationException('error.account_disabled');
        }

        return $user;
    }

    /**
     * {@inheritdoc}
     */
    public function checkCredentials($credentials, UserInterface $user)
    {
        return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
    }

    /**
     * {@inheritdoc}
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {

        if ($targetPath = $this->getTargetPath($request->getSession(), $providerKey)) {
            return new RedirectResponse($targetPath);
        }

        $urlName = $this->authorizationChecker->isGranted(Users::ROLE_USER) ? 'front_index' : 'index_front';


        return new RedirectResponse($this->router->generate($urlName));
    }

    /**
     * {@inheritdoc}
     */
    protected function getLoginUrl()
    {
        return $this->router->generate('front_login');
    }
}

Merci d'avance pour votre aide

3 Réponse

87112
,

Hello,

Quel est l'intérêt de créer deux guards?
Il ne faut pas mélanger le process d'authentification avec celui d'autorisation. Tu as un seul provider (ton entité User), donc tous tes users sont dans la même base si je ne m'abuse? Donc l'authentification s'effectue de la même manière non?
Ce sont les autorisations qui changent, suivant le rôle dont ton utilisateur dispose...
Tu pourrais donc avoir un seul guard, qui permet l'authentification, et ensuite tu te bases sur le rôle pour gérer les autorisations (ce que tu fais déja très bien dans la section access_control)....
Un autre guard serait utile, si par exemple, tu avais un second provider (oAuth, LDAP...) et donc un process d'authentification différent.

169209
,

Au fait je penser à deux guard pour le fait que l'autre est à l'user back et l'autre à l'user front, puis comme tu le dis un seul suffit et j'ai enlever l'autre, maintenant c'est la vue qui pose problème car si je tape /admin je suis toujours rediriger vera /login au lieu de login-front
j'aimerai juste que les user front ai leur vue pour le login et la même chose pour les user back

87112
,

Pourquoi 2 vues différentes pour la même chose (authentification)? Ce qui est intéressant c'est de rediriger ensuite par défaut sur une page différente (ou sur la page précédement demandée)...