FOSUserBundle <3's Guard Authenticators

We now understand that FOSUserBundle just gives us a nice User class and some routes & controllers for registration, reset password, edit profile and a few other things. The bundle does not provide any authentication. Open app/config/security.yml. The form_login authentication mechanism we're using is core to Symfony itself, not this bundle.

So, one of the questions we get a lot is: how can I use Guard authentication with FOSUserBundle? It turns out, it's simple! Guard authentication and FOSUserBundle solve different problems, and they work together beautifully. Teamwork makes the dream work!

But, why would you want to use Guard authentication with FOSUserBundle? Well, as easy as form_login is, it's a pain to customize. Guard is more work up front, but gives you a lot more control. You can also use Guard to add some sort of API authentication on top of form_login.

Creating the Authenticator

Let's replace form_login with a more flexible Guard authenticator. At the root of our project, you should have tutorial/ directory with a file called LoginFormAuthenticator.php. In src/AppBundle, create a new directory called Security and paste that file here.

<?php
... line 2
namespace AppBundle\Security;
... lines 4 - 19
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 22 - 101
}

This LoginFormAuthenticator is almost an exact copy of the authenticator we created in our Symfony Security tutorial. I've just added CSRF token checking - since our HTML login form has a CSRF token in it - and made a few other minor tweaks. For example at the bottom, I updated the login route name to use the one from FOSUserBundle.

The authenticator is very straightforward: It looks for the submitted _username and _password fields from the login form. It doesn't care if you built that login form yourself, or if it comes from FOSUserBundle. Then, it queries for your User object by email only and checks to see if the password is valid. Obviously you can write your authenticator to do anything.

Registering the Authenticator

To get this to work, like all authenticators, we need to register it as a service. I'll add app.security.login_form_authenticator, set the class to LoginFormAuthenticator and use autowire: true.

... lines 1 - 5
services:
... lines 7 - 28
app.security.login_form_authenticator:
class: AppBundle\Security\LoginFormAuthenticator
autowire: true

Copy that service ID. Then open app/config/security.yml. Ok, let's comment-out form_login entirely. And instead, add guard, authenticators, then paste the service ID.

... lines 1 - 2
security:
... lines 4 - 12
firewalls:
... lines 14 - 18
main:
... lines 20 - 27
guard:
authenticators:
- app.security.login_form_authenticator
# form_login:
# csrf_token_generator: security.csrf.token_manager
... lines 34 - 38

That's it! FOSUserBundle doesn't care who or what is processing the login form submit.

Let's try it! Click log out, click login and login with [email protected] Yea, this does still say "Username", but we know that our authenticator actually logs us in via email. So, we'll want to tweak that language. Use the password admin and... boom!

Congrats! You just used a Guard authenticator with FOSUserBundle. Wasn't that nice? You should feel empowered to use FOSUserBundle because you want things like a registration page or reset password system. But, you can still take control of your actual login mechanism and do whatever the heck you want.

The last part of this bundle that you'll need to customize are the emails: the reset password email and the registration confirmation email, if you want to send that one. The docs are good on this topic, and it's mostly a matter of overriding templates... which we already mastered.

All right guys, go use FOSUserBundle to quickly bootstrap your site! As long as you understand what it does... and does not give you, it's awesome. Seeya next time!

Leave a comment!

  • 2019-03-28 Victor Bocharsky

    Hey Elvism,

    Well, it works in a slightly different way - see form errors instead. You can find what exactly was wrong there. This might be helpful for you I think:
    https://symfonycasts.com/bl...

    Cheers!

  • 2019-03-28 Elvism

    Hi Victor,

    Thanks for clarifying, so it's the opposite from what I was thinking.

    Now this prompts me to another question. If $form->isValid() will return me only a boolean (true/false) how can I know exactly what failed in my form? If CSRF protection is what has failed, I want to get a "CSRF invalid token" message on the login form.

    I'm looking for something like $form->isValidCSRF() or a way to verify the submitted token by this form which is created using the Symfony's Form builder.

    Cheers,

  • 2019-03-26 Victor Bocharsky

    Hey Elvism,

    Wait, you do need to call $form->isValid() somewhere before you will extract some data from the submitted form. CSRF protection is checked on isValid() call, and you should call it, Symfony does not call this method internally by itself.

    Cheers!

  • 2019-03-25 Elvism

    Hi Victor,

    Thanks for the feedback! So that means I DO NOT need to check $form->isValid() call inside my `LoginFormAuthenticator` class, my Symfony form will internally check for it as it comes built-in, and enabled by default. Is my assumption correct?

  • 2019-03-25 Victor Bocharsky

    Hey Elvism,

    If you use Symfony Forms for it - it will automatically check the CSRF protection on $form->isValid() call, so as long as you call isValid() method - you don't need to check for CSRF manually, just make sure that the CSRF protection is enabled for that form - you should have that hidden _token field rendered.

    P.S. Symfony Forms FTW! :)

    Cheers!

  • 2019-03-24 Elvism

    I replaced the default login form from `FOSUserBundle` by a form created with the Symfony form builder. Here is the code for this new login form:

    namespace App\Form;

    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\Form\Extension\Core\Type\EmailType;
    use Symfony\Component\Form\Extension\Core\Type\PasswordType;
    use Eo\HoneypotBundle\Form\Type\HoneypotType;
    use Symfony\Component\OptionsResolver\OptionsResolver;

    class LoginFormType extends AbstractType
    {
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
    $builder
    ->add('_username', EmailType::class)
    ->add('_password', PasswordType::class)
    ->add('companyName', HoneypotType::class)
    ;
    }


    public function configureOptions(OptionsResolver $resolver)
    {
    $resolver->setDefaults([
    'translation_domain' => 'FOSUserBundle'
    ]);
    }

    }

    Since I'm already generating my form using the Symfony's form builder mechanism, and this one gives me out of the box CSRF protection. Do I still need to get and check the CSRF token in my Guard `LoginFormAuthenticator` with this?

    $csrfToken = $request->request->get($form->getName())['_token'];
    if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken('authenticate', $csrfToken))) {
    throw new InvalidCsrfTokenException('Invalid CSRF token.');
    }

  • 2019-02-07 Diego Aguiar

    Oh, that comes from extending AbstractFormLoginAuthenticator class, you only have to implement such method and that should be it (But I may be wrong because you are on such an old Symfony version)

  • 2019-02-07 Lavin Bassam

    question again so... how i use the script? copy paste in LoginFormAuthenticator? or i make a new php file?

    EDIT: found it where to save that php file and i got a new error message

    FatalErrorException in LoginFormAuthenticator.php line 20:
    Error: Class AppBundle\Security\LoginFormAuthenticator contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator::getDefaultSuccessRedirectUrl)

    what line makes this error is this code line

    class LoginFormAuthenticator extends AbstractFormLoginAuthenticator

  • 2019-02-05 Diego Aguiar

    Ohh that's because Disqus thinks that the ending parentheses is part of the URL, just remove it, or click here: https://github.com/symfony/...

  • 2019-02-05 Lavin Bassam

    hello im using symfony 2.8.3 and the link you give me it not currently active again

  • 2019-02-05 weaverryan

    Hey Lavin Bassam!

    Hmm. What version of Symfony are you using? I can see from your error that you ARE referring to that trait correctly (https://github.com/symfony/... so there is no reason it should be missing. Let me know!

    Cheers!

  • 2019-02-04 Lavin Bassam

    hello guys another question from me again

    when i try to test on url /login i got this error message
    ClassNotFoundException in LoginFormAuthenticator.php line 22:
    Attempted to load trait "TargetPathTrait" from namespace "Symfony\Component\Security\Http\Util".
    Did you forget a "use" statement for another namespace?

    so what file i must modifying to solve this issue?

  • 2018-12-03 Victor Bocharsky

    Hey Elvism,

    No, all services should be autowired as also as private by default in Symfony 4.2

    Cheers!

  • 2018-12-02 Elvism

    As of Symfony 4.2, is it still required to put autowire: true when registering the service?

  • 2018-11-05 Diego Aguiar

    Hey Chad Meyers

    Sorry for the delay. I'm not sure if I understand you right. Before adding FOSUserBundle your authenticator was working fine but after installing you started seeing that error?

  • 2018-11-01 Chad Meyers

    I am getting the error "You must configure the check path to be handled by the firewall using form_login in your security firewall configuration." with Symfony 4 - and after updating the LoginFormAuthenticator.php

    I had login logout working via the "bin/console make:auth" but want to use the extra features from FOSUser. I can't get guard: authenticators: to work

  • 2018-10-13 weaverryan

    Hey Timothy Jupe!

    Yes! You've actually done a really nice job summarizing the changes. Here are some details to help:

    1) In Symfony 3, supports() didn't exist - only getCredentials()

    2) In Symfony 4, getCredentials() was split into 2 methods: getCredentials() and supports(). Supports() controls whether or not your authenticator should be used on this request.

    Basically, you'll move the beginning of getCredentials() into supports, but return true/or false


    public function supports(Request $request)
    {
    $isLoginSubmit = $request->getPathInfo() == '/login_check' && $request->isMethod('POST');
    if (!$isLoginSubmit) {
    // skip authentication
    return false;
    }

    return true;
    }

    Then, the first lines you'll need in getCredentials() will be the $username = lines.

    Let me know if that helps! I hope it keeps you moving ;). Btw, you should also check out the new "php bin/console make:auth" command - as of about 1 hour ago, it can generate you a full login form system (without even needing FOSUserBundle).

    Have fun! Cheers!

  • 2018-10-13 Timothy Jupe

    A coupe of issues Im having, using Symfony 4.First IntelliJ is unhappy with the line
    - "class LoginFormAuthenticator extends AbstractFormLoginAuthenticator" with "class must be declared abstract or implement method supports" ... I now notice there is no supports method in this example, as there is in the new Symfony 4 Security tutorial. It seems like you've put the path/POST check in getCredentials.
    - This in turn is highlighting the "return" in getCredentials and saying "Missing return argument".

    I've updated the other parts of this LoginFormAuthenticator for Symfony 4, but not sure what to do about the above parts.

  • 2018-07-30 Diego Aguiar

    Hey Dennis de Best

    Hmm, that's strange... let's double check a couple of things first

    - The security config should live in


    // security.yaml
    security:
    firewalls:
    guard:
    authenticators:
    - Your/Authenticator


    - The "Authenticator" service should implement AbstractFormLoginAuthenticator interface
    - You must create a "login/logout" route

  • 2018-07-30 Dennis de Best

    Hey, I'm trying to implement FOSUserBundle with guard on Symfony 4.1.

    As steevenDS pointed out there are 2 errors with the constructor type hints : EntityManager and PasswordEncoder that need to be set to their Interface counterparts.

    A new function needs to be implemented as well, the supports function, we remove the isLoginSubmit logic from getCredentials and put it inside this new function :

    /**
    * 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;
    }
    }

    Once all that is done, the old configuration commented out and replaced by :

    guard:
    authenticators:
    - App\Security\LoginFormAuthenticator:

    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/cur...

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

    Any advice would be very much appreciated.

  • 2018-06-18 Victor Bocharsky

    Hey Steeven,

    Haha, well, if you don't see an error - it should work, but would be cool to have a functional test on it to be sure ;) Let us know if it does not work.

    Cheers!

  • 2018-06-18 SteevenDS

    I've just replace in constructor the typehint for $em with EntityManagerInterface and $passwordEncoder with UserPasswordEncoderInterface. Thanks, don't have errors now, but i don't know if it's working ! ;o)

  • 2018-06-18 Victor Bocharsky

    Hey Steeven,

    Hm, try to typehint your $em argument with "Doctrine\ORM\EntityManagerInterface" interface as suggested in error message, do you still have the same problem? Not sure 100% but probably Doctrine\ORM\EntityManager alias was removed that caused some BC breaks.

    Cheers!

  • 2018-06-17 SteevenDS

    Hi, thanks for this awesome tutorial, i'm working with symfony 3.4, FOSUserBundle and guard it's not working, i don't know why, i have tried to found solutions but nothing...

    In security.yml, i have

    main:
    form_login:
    provider: fos_userbundle
    csrf_token_generator: security.csrf.token_manager

    if i replace this with

    guard:
    authenticators:
    - app.security.login_form_authenticator

    i got this error:

    Cannot autowire service "app.security.login_form_authenticator": argument "$em" of method "AppBundle\Security\LoginFormAuthenticator::__construct()" references class "Doctrine\ORM\EntityManager" but no such service exists. Try changing the type-hint to one of its parents: interface "Doctrine\ORM\EntityManagerInterface", or interface "Doctrine\Common\Persistence\ObjectManager".

    Thanks and sorry for my bad english.

  • 2017-07-28 Diego Aguiar

    Hey Miguel Plazas!

    We are glad to hear you are liking our tutorials :)
    You may want to watch our tutorial about OAuth2 (is really helpful): https://knpuniversity.com/s...

    Have a nice day

  • 2017-07-28 Miguel Plazas

    Hi, this tutorial is aweosme, but, is possible show how to make a RestFul authentication? Thanks

  • 2017-06-26 weaverryan

    Any my fault for the slow reply really :). As I understand it, AdminLTE is basically a theme for creating an admin section, correct? What specifically would you like to see in a tutorial? How to integrate AdminLTE in a Symfony app? I'm curious!

    Cheers!

  • 2017-06-22 Victor Bocharsky

    Hey Piotr,

    Thanks for this idea! I like AdminLTE in general - it has tons of features out-of-the-box. I have added it to our idea list, but not sure yet when it could be released, we have a bit different plans for the nearest future. :)

    Cheers!

  • 2017-06-22 Piotr

    any respond, please? cheers! : -)

  • 2017-06-07 Piotr

    hi, may it be interesting to handle FOSuserBundle with adminLTE together for building interesting aps, for data visualization with HighCharts for example.

    https://adminlte.io/themes/...
    https://www.highcharts.com/...

    best regards, for great tutorials