1
votes

I'm trying to implement FOSUserBundle with guard on Symfony 4.1. This is my authenticator :

<?php


namespace App\Security;

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\Encoder\UserPasswordEncoderInterface;
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 extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;

private $em;
private $router;
private $passwordEncoder;
private $csrfTokenManager;

public function __construct(EntityManagerInterface $em, RouterInterface $router, UserPasswordEncoderInterface $passwordEncoder, CsrfTokenManagerInterface $csrfTokenManager)
{
    $this->em = $em;
    $this->router = $router;
    $this->passwordEncoder = $passwordEncoder;
    $this->csrfTokenManager = $csrfTokenManager;
}

public function getCredentials(Request $request)
{
    $username = $request->request->get('_username');
    $password = $request->request->get('_password');
    $csrfToken = $request->request->get('_csrf_token');

    if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken('authenticate', $csrfToken))) {
        throw new InvalidCsrfTokenException('Invalid CSRF token.');
    }

    $request->getSession()->set(
        Security::LAST_USERNAME,
        $username
    );

    return [
        'username' => $username,
        'password' => $password,
    ];
}

public function getUser($credentials, UserProviderInterface $userProvider)
{
    $username = $credentials['username'];

    return $this->em->getRepository('App:User')
        ->findOneBy(['email' => $username]);
}

public function checkCredentials($credentials, UserInterface $user)
{
    $password = $credentials['password'];

    if ($this->passwordEncoder->isPasswordValid($user, $password)) {
        return true;
    }

    return false;
}

public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
    $targetPath = null;

    // if the user hit a secure page and start() was called, this was
    // the URL they were on, and probably where you want to redirect to
    $targetPath = $this->getTargetPath($request->getSession(), $providerKey);

    if (!$targetPath) {
        $targetPath = $this->router->generate('homepage');
    }

    return new RedirectResponse($targetPath);
}

protected function getLoginUrl()
{
    return $this->router->generate('fos_user_security_login');
}

/**
 * Does the authenticator support the given Request?
 *
 * If this returns false, the authenticator will be skipped.
 *
 * @param Request $request
 *
 * @return bool
 */
public function supports(Request $request)
{
    if($request->attributes->get('_route') !== 'fos_user_security_login'){
        return false;
    }
}
}

this is my security.yaml file :

security:
    encoders:
        App\Entity\User: bcrypt

    providers:
        fos_userbundle:
            id: fos_user.user_provider.username
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            anonymous: ~
            logout: ~
            guard:
                authenticators:
                    - App\Security\LoginFormAuthenticator
#            form_login:
#               csrf_token_generator: security.csrf.token_manager
            remember_me:
                secret: '%env(APP_SECRET)%'

    access_control:
        # - { path: ^/admin, roles: ROLE_ADMIN }
        # - { path: ^/profile, roles: ROLE_USER }

And the fos_user.yaml :

fos_user:
    db_driver: 'orm'
    user_class: App\Entity\User
    firewall_name: main
    service:
        mailer: fos_user.mailer.twig_swift
    from_email:
        address: "[email protected]"
        sender_name: "test"
    registration:
        form:
            type: App\Form\UserRegistrationFormType

Whenever I go to login I get the following error :

You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.

I have the security-bundle installed and tried going through the steps provided in the official documentation : https://symfony.com/doc/current/security/guard_authentication.html

And I followed the tutorial on KnpUniversity but that was for Symfony 3 and it seems things do not work the same way anymore.

There must be something really obvious I'm not seeing.

Any advice would be very much appreciated.

Version info : - Symfony ^4.1 - FOSUserBundle ^2.1 - security-guard ^4.1

3
Just a stab in the dark but I think you need to specify the form_login -- see (symfony.com/doc/current/security/form_login_setup.html) as an exampleAlex.Barylski
Thanks for the reply I'll check it out and let you knowDennis de Best
Did you ever solve the error? I am having the exact same problem. What did you do?Chad

3 Answers

0
votes

I think this will help you understand better what is symfony guard and fos user bundle. Symfony Guard - You don't need FOSUserBundle

0
votes

HINT: I had an issue similar. If I remember correctly, you have to remove the route for the login page that is defined in the fos user bundle routes. I know I am being ambiguous, but check the routes. It's looking for a fosuser login page, while you are authenticating with guard.

0
votes

To help fill out Adrian's answer, I think your login route should be fos_user_security_check instead of fos_user_security_login.

The supports() function in Symfony 4 will check the route after you submitted the form, which will go to login_check by default with FOSUserBundle, and then it will run your custom authenticator instead.

This might also be the reason why Symfony2 can work, because they hadn't yet added supports() back then.