Search code examples
phpformssymfonyauthenticationsymfony-guard

Symfony's Guard start() function not working


I'm new to Symfony and I'm learning how to use Guard to authenticate users. Currently using Symfony 2.8.4.

You can check all my code below, it's mostly from various tutorials I found online.

It seems the start() function from the FormLoginAuthenticator class isn't properly detecting if credentials were sent or not. If I visit any page like /login, /homepage, /login_check, etc.. I get the same return on all of them:

{"message":"Username could not be found."}

Here's my FormLoginAuthenticator.php:

namespace AppBundle\Security;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Doctrine\ORM\EntityManager;

class FormLoginAuthenticator extends AbstractGuardAuthenticator
{
    private $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    /*
     * This method will get called on every request that requires an authentication.
     * Its job is to read the authentication information contained in the request, and return it.
     */
    public function getCredentials(Request $request)
    {
        return array(
            'username' => $request->request->get('_username'),
            'password' => $request->request->get('_password'),
        );
    }

    /*
     * After getting the credentials, try to get the User associated with those credentials.
     * The value of the credentials is passed to getUser() as the $credentials argument.
     * The job of this method is to return an object implementing UserInterface.
     * If it does, the next step of the authentication will be called: checkCredentials().
     * Else, the authentication will fail and the method onAuthenticationFailure() will get called.
     */
    public function getUser($credentials, UserProviderInterface $userProvider)
    {
        return $userProvider->loadUserByUsername($credentials['username']);
    }

    /*
     * The job of this method is to check if the credentials of the previously returned User are correct.
     * If it returns true, the user will be authenticated, and the method onAuthenticationSuccess() will be called.
     * If does not, the authentication fails and the method onAuthenticationFailure() is called.
     */
    public function checkCredentials($credentials, UserInterface $user)
    {
        $plainPassword = $credentials['password'];
        $encoder = $this->container->get('security.password_encoder');
        if (!$encoder->isPasswordValid($user, $plainPassword)) {
            throw new BadCredentialsException();
        }
    }

    /*
     * This method is called when the user is successfully authenticated.
     * It can return null, in which case the request continues to process as expected,
     * or return a Response object, in which case this Response will be transferred to the user.
     */
    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
    {
        $url = $this->router->generate('homepage');

        return new RedirectResponse($url);
    }

    /*
     * This method is called when the authentication fails.
     * Its job is to return a Response object that will be sent to the client.
     */
    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
    {
        $data = array(
            'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
        );

        return new JsonResponse($data, 403);
    }

    /*
     * This gets called when the user tries to access a resource that requires authentication,
     * but no authentication information was found in the request.
     */
    public function start(Request $request, AuthenticationException $authException = null)
    {
        $url = $this->router->generate('security_login');
        return new RedirectResponse($url);
    }

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

    protected function getDefaultSuccessRedirectUrl()
    {
        return $this->container->get('router')
            ->generate('homepage');
    }

    public function supportsRememberMe()
    {
        return false;
    }
}

Here's my SecurityController.php:

namespace AppBundle\Controller;

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class SecurityController extends Controller
{
    /**
     * @Route("/login", name="security_login")
     */
    public function loginAction()
    {
        $helper = $this->get('security.authentication_utils');
        return $this->render('security/login.html.twig', array(
            'error' => $helper->getLastAuthenticationError(),
        ));
    }

    /**
     * @Route("/login_check", name="security_login_check")
     */
    public function loginCheckAction()
    {
        // will never be executed
    }
}

The security.yml file:

security:
    providers:
        your_db_provider:
            entity:
                class: AppBundle:User

    encoders:
        AppBundle\Entity\User: bcrypt

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

        main:
            anonymous: ~
            logout: ~

            guard:
                authenticators:
                    - app.form_login_authenticator

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/, roles: ROLE_ADMIN }

And finally the services.yml file:

parameters:

services:
    app.form_login_authenticator:
        class: AppBundle\Security\FormLoginAuthenticator
        arguments: ['@doctrine.orm.entity_manager']

Solution

  • Adding this to getCredentials() solves this particular problem but there are many more to fix:

    if ($request->getPathInfo() != '/login_check') {
        return;
    }