Buy

Authenticator: getUser, checkCredentials & Success/Failure

Here's the deal: if you return null from getCredentials(), authentication is skipped. But if you return anything else, Symfony calls getUser():

53 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 8
use Symfony\Component\Security\Core\User\UserProviderInterface;
... lines 10 - 11
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 14 - 36
public function getUser($credentials, UserProviderInterface $userProvider)
{
}
... lines 40 - 51
}

And see that $credentials argument? That's equal to what we return in getCredentials(). In other words, add $username = $credentials['_username']:

60 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 12
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 15 - 39
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['_username'];
... lines 43 - 45
}
... lines 47 - 58
}

I do continue to call this username, but in our case, it's an email address. And for you, it could be anything - don't let that throw you off.

Hello getUser()

Our job in getUser() is... surprise! To get the user! What I mean is - to somehow return a User object. Since our Users are stored in the database, we'll query for them via the entity manager. To get that, add a second constructor argument: EntityManager $em:

60 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 12
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 15 - 17
public function __construct(FormFactoryInterface $formFactory, EntityManager $em)
{
... lines 20 - 21
}
... lines 23 - 58
}

And once again, I'll use my Option+Enter shortcut to create and set that property:

60 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 12
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... line 15
private $em;
public function __construct(FormFactoryInterface $formFactory, EntityManager $em)
{
... line 20
$this->em = $em;
}
... lines 23 - 58
}

Now, it's real simple: return $this->em->getRepository('AppBundle:User')->findOneBy() with email => $email:

60 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 12
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 15 - 39
public function getUser($credentials, UserProviderInterface $userProvider)
{
$username = $credentials['_username'];
return $this->em->getRepository('AppBundle:User')
->findOneBy(['email' => $username]);
}
... lines 47 - 58
}

Easy. If this returns null, guard authentication will fail and the user will see an error. But if we do return a User object, then on we march! Guard calls checkCredentials():

60 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 12
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 15 - 47
public function checkCredentials($credentials, UserInterface $user)
{
}
... lines 51 - 58
}

Enter checkCredentials()

This is our chance to verify the user's password if they have one or do any other last-second validation. Return true if you're happy and the user should be logged in.

For us, add $password = $credentials['_password']:

67 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 12
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 15 - 47
public function checkCredentials($credentials, UserInterface $user)
{
$password = $credentials['_password'];
... lines 51 - 56
}
... lines 58 - 65
}

Our users don't have a password yet, but let's add something simple: pretend every user shares a global password. So, if ($password == 'iliketurtles'), then return true:

67 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 49
$password = $credentials['_password'];
if ($password == 'iliketurtles') {
return true;
}
return false;
... lines 57 - 67

Otherwise, return false: authentication will fail.

When Authentication Fails? getLoginUrl()

That's it! Authenticators are always these three methods.

But, what happens if authentication fails? Where should we send the user? And what about when the login is successful?

When authentication fails, we need to redirect the user back to the login form. That will happen automatically - we just need to fill in getLoginUrl() so the system knows where that is:

67 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 12
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 15 - 58
protected function getLoginUrl()
{
}
... lines 62 - 65
}

But to do that, we'll need the router service. Once again, go back to the top and add another constructor argument for the router. To be super cool, you can type-hint with the RouterInterface:

72 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 8
use Symfony\Component\Routing\RouterInterface;
... lines 10 - 13
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 16 - 19
public function __construct(FormFactoryInterface $formFactory, EntityManager $em, RouterInterface $router)
{
... lines 22 - 24
}
... lines 26 - 70
}

Use the Option+Enter shortcut again to set up that property:

72 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 13
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 16 - 17
private $router;
public function __construct(FormFactoryInterface $formFactory, EntityManager $em, RouterInterface $router)
{
... lines 22 - 23
$this->router = $router;
}
... lines 26 - 70
}

Down in getLoginUrl(), return $this->router->generate('security_login'):

72 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 13
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 16 - 61
protected function getLoginUrl()
{
return $this->router->generate('security_login');
}
... lines 66 - 70
}

When Authentication is Successful?

Tip

Due to a change in Symfony 3.1, you can still fill in getDefaultSuccessRedirectUrl() like we do here, but it's deprecated. Instead, you'll add a different method - onAuthenticationSuccess() - we have the code in a comment: http://bit.ly/guard-success-change

So what happens when authentication is successful? It's awesome: the user is automatically redirected back to the last page they tried to visit before being forced to login. In other words, if the user tried to go to /checkout and was redirected to /login, then they'll automatically be sent back to /checkout so they can continue buying your awesome stuff.

But, in case they go directly to /login and there is no previous URL to send them to, we need a backup plan. That's the purpose of getDefaultSuccessRedirectUrl():

72 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 13
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 16 - 66
protected function getDefaultSuccessRedirectUrl()
{
... line 69
}
}

Send them to the homepage: return $this->router->generate('homepage'):

72 lines src/AppBundle/Security/LoginFormAuthenticator.php
... lines 1 - 13
class LoginFormAuthenticator extends AbstractFormLoginAuthenticator
{
... lines 16 - 66
protected function getDefaultSuccessRedirectUrl()
{
return $this->router->generate('homepage');
}
}

The authenticator is done. If you need even more control over what happens on error or success, there are a few other methods you can override. Or check out our Guard tutorial. Let's finally hook this thing up.

Registering the Service

To do that, open up app/config/services.yml and register the authenticator as a service.

Tip

If you're using Symfony 3.3, your app/config/services.yml contains some extra code that may break things when following this tutorial! To keep things working - and learn about what this code does - see https://knpuniversity.com/symfony-3.3-changes

Let's call it app.security.login_form_authenticator. Set the class to LoginFormAuthenticator and because I'm feeling super lazy, autowire the arguments:

21 lines app/config/services.yml
... lines 1 - 5
services:
... lines 7 - 17
app.security.login_form_authenticator:
class: AppBundle\Security\LoginFormAuthenticator
autowire: true

We can do that because we type-hinted all the constructor arguments.

Configuring in security.yml

Finally, copy the service name and open security.yml. To activate the authenticator, add a new key under your firewall called guard. Add authenticators below that, new line, dash and paste the service name:

28 lines app/config/security.yml
... lines 1 - 2
security:
... lines 4 - 9
firewalls:
... lines 11 - 15
main:
... line 17
guard:
authenticators:
- app.security.login_form_authenticator
# activate different ways to authenticate
... lines 22 - 28

As soon as we do that, getCredentials() will be called on every request and our whole system should start singing.

Let's try it! Try logging in with weaverryan+1@gmail.com, but with the wrong password.

Beautiful! Now try the right password: iliketurtles.

Debugging with intercept_redirects

Ah! Woh! It did redirect to the homepage as if it worked, but with a nasty error. In fact, authentication did work, but there's a problem with fetching the User from the session. Let me prove it by showing you an awesome, hidden debugging tool.

Open up config_dev.yml and set intercept_redirects to true:

49 lines app/config/config_dev.yml
... lines 1 - 12
web_profiler:
... line 14
intercept_redirects: true
... lines 16 - 49

Now, whenever the app is about to redirect us, Symfony will stop instead, and show us the web debug toolbar for that request.

Go to /login again and login in with weaverryan+1@gmail.com and iliketurtles. Check this out: we're still at /login: the request finished, but it did not redirect us yet. And in the web debug toolbar, we are logged in as weaverryan+1@gmail.com.

So authentication works, but there's some issue with storing our User in the session. Fortunately, that's going to be really easy to fix.

Leave a comment!

  • 2017-08-16 Hugo Le Goff

    Thank you, you helped a lot for a part.

    But I still have to login the user through the API before checking the database. but the login step should happen in the tour (tell me if I'm wrong). Moreover I can't find the users in the database with the ids they gave because they can change them on the API owners' website then I only have an uuid as a static id that I can only get through the API.

    Therefore, should I call the API on getCredentials and just use the checkCredentials to return the result of the authentication (with return true/false) ?

    Thank you so much for what you do !

  • 2017-08-16 weaverryan

    Yo Hugo Le Goff!

    Haha, well, I'm happy you gave us a try :). If it helps, very soon, we will add English subtitles to all our videos.

    Ok, about your situation! If I understand correctly, the difference between this tutorial and your situation is that you want to create the user if it does not exist. Is that correct? If so, I'm happy to say it's an easy tweak! In getUser(), simply create and save a new User if none exists, something like:


    public function getUser($credentials, UserProviderInterface $userProvider)
    {
    $username = $credentials['_username'];
    $user = $this->em->getRepository('AppBundle:User')
    ->findOneBy(['email' => $username]);

    if ($user) {
    // cool! We found an existing user!
    return $user;
    }

    // oh well, let's just create a new user!
    $user = new User();
    $user->setUsername($username);
    // set any other information... though you don't know much about the user :)
    $this->em->persist($user);
    $this->em->flush();

    return $user;
    }

    That's it! Let me know if it helps :).

    Cheers!

  • 2017-08-16 weaverryan

    Haha, well, happy you got it working one way or another. I think it was still a good question ;).

    Cheers!

  • 2017-08-16 Hugo Le Goff

    Hi !

    Thank you for your awesome tutorials, even if I'm french, these are clearly better than french ones.

    I'm using an API to authenticate users on my website but I also store them in database (without ids of course) but I'm actually stuck because I have to :
    - Call the authenticator of API (with username and password)
    - Check result
    - If true, look if the user is in the database
    - Get user or create him

    But I don't know if the way you show on this tutorial is the good beacause the steps don't match with mine.

    If you could enlighten me, redirect me to another method or tell me how to apply what I want to do to yours, I would be very grateful to you.

    Thank you.

  • 2017-08-15 Adam

    Thanks for the quick response, Ryan! I actually don't go back to check what the value of the action was after I got it working, but yeah, duh -- I should have known that's the behavior of a submit button. Still not sure what was blocking the submit button from doing anything, but I'm over it at this point. May replicate and document at some point, will do so here.

  • 2017-08-15 weaverryan

    Hey Adam!

    It's actually a really good question - and a very good detail to notice! Yes, the action= on the form tag is blank. That's on purpose. When a form tag has no action, your browser knows to submit the form right back to the same URL. Since that's what we want, not worrying about setting it is kind of a shortcut :). You *can* set it to a different URL if you need to: https://symfony.com/doc/cur...

    Cheers and keep up the good work!

  • 2017-08-13 Adam

    Ignore this (kind of). I started from scratch, ignored everything in the Script part of each tutorial's page and stayed doggedly loyal to the code happening in the video with a few exceptions. The video and current symfony docs for "Traditional login form" differ a bit.

  • 2017-08-13 Adam

    There should be an 'action' on the Login form, right? I get nothing, confirmed by the looking at the debugger which shows action is blank.
    I've gone back through the last several chapters and even compared my code to the "finish" code in the download, but I can't see any differences. Anyone have ideas of where to look?
    Why on the LoginForm->buildForm do we tell it which fields we need, but not what action to take? This seems strange to me.

    I was crushing through all the Learn Symfony tutorials so quickly, and this is the first show-stopper snag I've hit!

  • 2017-07-31 weaverryan

    Haha, yes! Glad you found it so quickly. Now, keep going! :D

  • 2017-07-31 Nina

    Answer in the next tutorial )

  • 2017-07-31 Nina

    Hello, please help me
    I have error
    "There is no user provider for user "AppBundle\Entity\User"

    Why I have error and how to fix this?

  • 2017-06-21 Jennifer Koenig

    Yes, that was the problem! Thanks!

  • 2017-06-20 Diego Aguiar

    Hey Jennifer Koenig

    Look's like you type-hinted the wrong FormFactory type, Change the "FormFactoryBuilderInterface" by "Symfony\Component\Form\FormFactoryInterface"

    Cheers"

  • 2017-06-20 Jennifer Koenig

    I'm getting the error when loading my login page:

    Unable to autowire argument of type "Symfony\Component\Form\FormFactoryBuilderInterface" for the service "app.security.login_form_authenticator". No services were found matching this interface and it cannot be auto-registered.

    security.yml:

    security:
    ...
    firewalls:
    ...
    main:
    anonymous: ~
    guard:
    authenticators:
    - app.security.login_form_authenticator

    services.yml:

    services:
    ...
    app.security.login_form_authenticator:
    class: AppBundle\Security\LoginFormAuthenticator
    autowire: true

    LoginFormAuthenticator.php:

    public function __construct(FormFactoryBuilderInterface $formFactory, EntityManager $em, RouterInterface $router)
    {

    $this->formFactory = $formFactory;
    $this->em = $em;
    $this->router = $router;
    }

    what am I missing?

  • 2017-05-18 Mert Simsek

    Hi Ryan,

    I spent my last night. What method is now working when I memorized :)
    But I finally solved it. Now I can login to twitter with Symfony user. Thank you so much :))

  • 2017-05-17 weaverryan

    Yo Mert Simsek!

    So, it sounds like you already have your OAuth flow setup, and are even getting the access token and using it to fetch the Twitter username. Is that correct? It also sounds like you're doing this all inside of a Guard authenticator (good choice).

    If I'm right, then you are inside of getUser() and all you have is the Twitter username. In this situation, I typically do two things:

    1) On my User class, I add a twitterUsername property (actually, I usually store twitterId). So first, query your database to see if a User with this twitter username already exists. If it does, return it!

    2) If not, you need to create a new User and save it to the database. But typically, you *at least* need an email address to do this (the exact required fields you have on your User depend on your User). When I do things like login with Facebook, I make sure to choose a scope that *does* give me access to the user's email address. That way, I can use it to create the User object. The other "weird" thing is that your User probably has a password field. But at this point, obviously, your user has not set a password. The easiest thing to do is auto-generate a password for them and set it (or leave the password field blank). Basically, you're giving the user a password just to make your database happy - but you never give this password to your user. In this situation, your user cannot login with a password - they can only login with Twitter. If they want a password, they need to change (set) their password once logged in.

    Another alternative - that we use here on KnpU - is to NOT create the User object, but instead, redirect the user to a "registration" page. In Guard. This is actually a little bit tricky. I recommend creating a new Exception class - something like this: https://github.com/knpunive.... In getUser, when you realize that there is no User object yet, throw this exception. Then, in onAuthenticationFailure, look for this exception message. If it was the one that was thrown, save the user information (e.g. twitter id) to the session and redirect them to a registration page you've built. On that page, you can read the session information to, for example, make sure that the twitterUsername (or maybe twitterId) is set on the User before saving after the user submits the registration form. HEre's a partial example: https://gist.github.com/rwi.... This uses the KnpUOAuth2ClientBundle, which you are not using, but it should help you get some idea. The saveUserInfoToSession() method can be found here: https://github.com/knpunive...

    The tl;dr is: just create and save a User object! Yes, you may not have (at this moment) all the data about the user you'd like, so just fill in what you do have and leave the other fields blank. OR, redirect the user to a registration page.

    Cheers!

  • 2017-05-17 Mert Simsek

    I have a custom login page. Here is the twitter login button. Clicking on the button will take your twitter user. If this user is not in the database I add it. But I have only twitter username. How can i do symfony entry with this user? So how i can Symfony authenticated and authorization?

  • 2017-04-19 weaverryan

    Yo Jelle Schouwstra!

    Ok, now that that the authenticator's getCredentials() method is being called, we at least know we're there :).

    In getCredentials(), your job is to check the URL to see if the user is submitting the login form. If it is NOT, then you return nothing: this causes the authenticator to do nothing else - i.e. no other methods are called on your authenticator during this request. But if if the user *is* submitting the login form, then we grab the username & password from the request and return it. Because we're now returning *something* from getCredentials(), the the security system then calls the getUser() method on your authenticator.

    So the next question is, when you submit, is your getUser() method being called? Try the same trick with die to find out. If it is NOT, then check your logic in getCredentials to see why you're returning nothing when the login form is submitted. I suspect this is the problem, but let me know!

    You can also pass some of your code here - we might be able to spot any issues :).

    Cheers!

  • 2017-04-19 Jelle Schouwstra

    What else could stop the form from working?

  • 2017-04-18 Victor Bocharsky

    Hey Taffo,

    Let's figure it out!

    1. Do you have the file `src/AppBundle/Entity/User.php`?
    2. Does this file have a correct namespace `AppBundle\Entity`?
    3. Does this class name is `User`?

    If all these steps are correct - try to clear the cache.

    Please, ensure you have exactly the same letter case for everything I write here.

    Cheers!

  • 2017-04-17 Diego Aguiar

    I'm glad you could fix your problem :)

  • 2017-04-17 Jelle Schouwstra

    Thanks for pointing me in the right direction, I've found what did the form stopped from working (or never did in this case), I've made a typo in the security.yml file. I've managed to run the die() snippet and can now confirm that the getCredentials method is called.
    Now the form seems to process something for one second, but still doesn't anything besides that, and no errors are shown.

  • 2017-04-17 weaverryan

    Excellent! That helps us debug. Check out your security.yml config. If you have the guard then authenticators config (https://knpuniversity.com/s... then your getCredentials() method *will* be called on *every* request. If it's not being called, this is where you should look for the issue.

    Cheers!

  • 2017-04-17 Jelle Schouwstra

    I don't think it is, I placed the following snippets at either the beginning or end of the getCredentials method and nothing happens:

    die('i am here');

  • 2017-04-17 weaverryan

    Hey Jelle Schouwstra!

    Is your authenticator being called? I mean, if you, for example, add a die('i am here'); to the getCredentials() method of your authenticator, is it called? That's the first thing to check: if anything is wrong with registering your authenticator, then it won't be called, and your login form will simply be submitting back to itself with nothing else happening (so, nothing at all will happen on your form each time).

    Cheers!

  • 2017-04-17 Taffo

    Class 'AppBundle\Entity\User' does not exist

    Can anybody help?

  • 2017-04-17 Jelle Schouwstra

    My form doesn't work, if i fill in a user that exists it simply does nothing, if a user doesn't exist, nothing still happens...

  • 2017-04-15 Tak ToJest

    I have one problem after I activated the service :) namely I have error like this:



    Type
    error: Argument 1 passed to
    AppBundle\Security\LoginFormAuthenticator::__construct() must be an
    instance of Symfony\Component\Form\FormFactoryInterface, none given,
    called in
    C:\xampp\htdocs\symfony-security\start\var\cache\dev\appDevDebugProjectContainer.php
    on line 327

    Do I need to clear some cache?

    Ok I've got it. I screw up something in the services.yaml, Now it is working

  • 2017-03-04 weaverryan

    Nice work Carlo! That is the exact right solution! Basically, when autowiring doesn't work anymore, just don't use it! In future version of Symfony, we're adding some features to help with this - i.e. that allow you to use autowiring, but hint to Symfony the *one* problematic argument that can't be autowired. But, we'll talk about that here in the future - and this way of explicitly specifying the arguments will always work and be rock solid.

    Cheers!

  • 2017-03-03 Carlo Mario Chierotti

    Well, Ryan what I have to say...

    Symfony IS really a great piece of software!

    I just read carefully the error messages and I was able to give all the arguments to LoginFormAuthenticator! Here they are in all their glory, maybe someone else will have my same problem.

    app.security.login_form_authenticator:
    class: AppBundle\Security\LoginFormAuthenticator
    arguments:
    - '@form.factory'
    - '@doctrine.orm.default_entity_manager'
    - '@router.default'
    - '@security.user_password_encoder.generic'

    Have a nice weekend!

    Carlo

  • 2017-03-03 Carlo Mario Chierotti

    Hello Ryan,

    your solution for authentication & authorization is really great and I am using it in a couple of projects.

    Now I am working on a multilanguage project and I am trying to make it work along the JMSI18nRoutingBundle (https://github.com/schmittj....

    Unfortunately, I get this message:

    Unable to autowire argument of type "Symfony\Component\Routing\RouterInterface" for the service "app.security.login_form_authenticator". Multiple services exist for this interface (router.default, jms_i18n_routing.router).

    Autowiring is a sort of magic to me, so I cannot figure how to pass the arguments in services.yml or to tell the autowire not to consider the jms_i18n_routing.router.

    Can you please help me or tell me where to find more information on this subject?

    Thanks a lot!

    carlo

  • 2017-02-27 Victor Bocharsky

    You're welcome ;)

    Cheers!

  • 2017-02-25 ehymel

    Yep, you did say CompilerPass, and that went right over my head. I didn't realize that was something I had to implement, I thought you were just referring to the compiling process. Thanks for the link to the screencast and for your example. All is working now. Thanks for your patience!!

  • 2017-02-24 Victor Bocharsky

    Hey @ehymel,

    Yes, as I said, you have to do it with CompilerPass, because you can't do it via the "doctrine.orm" configuration and since this is a third party code - you don't have access to modifying their service definition code. We even has a screencast about Compiler Pass, check it out: https://knpuniversity.com/s...

    And I even have written my own compiler pass for EntityManager, so it should help you ;)


    class EntityManagerAutowiringTypePass implements CompilerPassInterface
    {
    public function process(ContainerBuilder $container)
    {
    $definition = $container->findDefinition('doctrine.orm.default_entity_manager');
    $definition->addAutowiringType(EntityManager::class);
    }
    }

    Cheers!

  • 2017-02-24 ehymel

    I looked again at the "Dealing with Multiple Implementations" link and followed their example. I see that I was misunderstanding 'autowiring_types'. Basically you attach this to a service definition when you want that service to be used as the default service when more than one service would otherwise match during dependency injection. Assuming that is right, then it makes sense.

    It seems like 'autowiring_types' would accomplish the *exact* same thing as the 'default_entity_manager' in the orm definitions, except of course that it does not.

    doctrine:
    orm:
    auto_generate_proxy_classes: "%kernel.debug%"
    default_entity_manager: default
    entity_managers:
    default:
    connection: default
    mappings:
    AppBundle: ~
    OldBundle: ~
    naming_strategy: doctrine.orm.naming_strategy.underscore
    auto_mapping: true
    historical:
    connection: historical
    mappings:
    OldBundle: ~

    I can't figure out is how to apply autowiring_types to the entity_manager definitions. If I add 'autowiring_types: doctrine.orm.entity_manager' under default, then I get an error:

    Unrecognized option "autowiring_types" under "doctrine.orm.entity_managers.default"

    As always, I appreciate your help.

  • 2017-02-23 Victor Bocharsky

    Hey @ehymel,

    The `autowiring_types` IS the way to solve this right until Symfony 3.3. Btw, `autowiring_types` isn't even deprecated in 3.2. Then starts from Symfony 3.3 you will be able to replace deprecated `autowiring_types` with `alias` - check this Symfony post: http://symfony.com/blog/new... . So everything will still work as it did before - no problems at all ;)

    Cheers!

  • 2017-02-22 ehymel

    Thanks, that makes sense, and the link you provided is helpful, even if to be deprecated in 3.3.

    Still, I have to wonder if there is a better way for me to implement this. If the autowiring_types is to be deprecated, then that leaves manually passing into services that require it. I have several services that require entity manager, and most require entity manager along with several other dependencies. It seems I would need to refactor all of these services to manually pass all required dependencies just to be able to get the default_entity_manager when needed. For example, LoginFormAuthenticator from this lesson requires entity manager along with 3 others (FormFactoryInterface, RouterInterface, UserPasswordEncoder). I guess I was hoping that Guard would use the "default" entity manager if not specified as suggested the documentation on setting up multiple entity managers.

    For now my solution is to disable Guard, use my historical_entity_manager to import old data, then disable (comment out) historical_entity_manager and re-enable Guard. I don't want to re-factor all other services. I'm certainly open to better ideas!!

    Thanks again for your help.

  • 2017-02-22 Victor Bocharsky

    Hey ehymel ,

    That's due to the "doctrine.orm.historical_entity_manager" which you created. The easiest fix here I think is to pass entity manager manually into those services which require it. Or you can fix it globally in CompilerPass setting `autowiring_types`, see the Dealing with Multiple Implementations of the Same Type article in Symfony docs how to do it, it has nice example.

    UPD: Keep in mind that the `autowiring_types` feature is deprecated since 3.3: https://symfony.com/blog/ne...

    Cheers!

  • 2017-02-20 Victor Bocharsky

    Hey Eric,

    Ah, it makes sense! And thank you for sharing it ;)

    Well, we do some simple debugging during some courses and use Symfony VarDumper component and Web Debug Toolbar. Unfortunately, we don't have any complete tutorial about debugging yet, but it's in out TODO list though.

    Cheers!

  • 2017-02-19 ehymel

    I'm having a problem implementing a second bundle in my application. Basically I have an existing database that I'd eventually like to pull data from to insert to my new db that is generated by symfony. I've created a new bundle, /src/OldBundle in addition to my original /src/AppBundle. In config.yml I've defined a second db connection and orm like so:

    doctrine:
    dbal:
    default_connection: default
    connections:
    default:
    driver: pdo_mysql
    host: "%database_host%"
    port: "%database_port%"
    dbname: "%database_name%"
    user: "%database_user%"
    password: "%database_password%"
    charset: UTF8
    historical:
    driver: pdo_mysql
    host: "%database_host%"
    port: "%database_port%"
    dbname: "%database_name2%"
    user: "%database_user%"
    password: "%database_password%"
    charset: UTF8

    orm:
    auto_generate_proxy_classes: "%kernel.debug%"
    default_entity_manager: default
    entity_managers:
    default:
    connection: default
    mappings:
    AppBundle: ~
    OldBundle: ~
    naming_strategy: doctrine.orm.naming_strategy.underscore
    auto_mapping: true
    historical:
    connection: historical
    mappings:
    OldBundle: ~
    naming_strategy: doctrine.orm.naming_strategy.underscore

    I've configured guard for authentication as per your tutorial here. After creating the second ORM I can no longer use the website. It complains:

    PHP Fatal error:  Uncaught Symfony\\Component\\DependencyInjection\\Exception\\RuntimeException: Unable to autowire argument of type "Doctrine\\ORM\\EntityManager" for the service "app.security.login_form_authenticator". Multiple services exist for this class (doctrine.orm.default_entity_manager, doctrine.orm.historical_entity_manager).

    It seems the autowire feature of the service is not selecting the "default" entity manager. Any ideas?

  • 2017-02-17 Eric

    Hi Victor,

    For sure! So the problems happen when I forget to put the return value in return roles of user entity as array values ["ROLE_USER"], instead I just put "ROLE_USER" there.

    As a matter of fact, from the error information shown on symfony, it is pretty hard to tell that the error lives in the entity declaration. I'm wondering if you can offer a tutorial on how to debug on symfony. Or if you have any suggestions on how to learn debugging on symfony. Any material links?

    Thank you so much!

    Eric

  • 2017-02-17 Victor Bocharsky

    Hey Eric,

    I'm glad you fixed it! Could you share your solution? What was the problem? I think it'll help others who have similar error.

    Cheers!

  • 2017-02-17 Eric

    I just solved the problem! :)

  • 2017-02-17 Eric

    It happens when I successfully log in. The following error appears:

    Catchable Fatal Error: Argument 3 passed to Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken::__construct() must be of the type array, string given, called in /Users/wenhan/Documents/Projects/Web Develop/symfony-security/start/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php on line 39 and defined

    Wondering how to solve it. THX!!!

  • 2017-02-13 Peter Tsiampas

    I like this. :)

  • 2017-01-16 Vince Liem

    I DID IT! Thank you so MUCH!! cheers.

  • 2017-01-16 weaverryan

    Hey Vince!

    I think you're close - you have done a lot of correct things (including 1 SecurityController with 2 actions). Here's the tricky part: you said that you have 2 entities... do both of these implement UserInterface? After your 2 types of users login, do they access completely different parts of the site? Or is there some overlap (the latter is probably the case - even if the 2 types of users might have access to different pages, probably there is some overlap on some of the pages they access). I'm asking because, *usually*, it makes sense to only have *one* User class, even if it feels like you have 2 different types of users. The reason is that, once you login, in a controller, you will say $this->getUser(). And if you have 2 User classes, then this could be one of 2 different objects, each with their own methods!

    Without knowing too much about your requirements, I'll make some assumptions and tell you how I *think* your setup should look:

    1) Have just one User class. You can have some sort of "flag" if needed on them to know if the user is an account manage or a contact.

    2) Configure only 1 user provider in security.yml - since now you only have one User class

    3) Have only 2 firewalls: dev and main. Register both authenticators under this 1 firewall. The reason you shouldn't have 2 firewalls is that I'm guessing that your 2 different types of users will go to some shared pages, and this doesn't work with separate firewalls. What I mean is, if 1 firewall is ^/contact and another is ^/account_manager, then if you login as an account manager and go to /account_manager/foo or even /bar, you won't be logged in anymore! Because you're not at a URL that's under your firewall. Btw, when you configure 2 authenticators under 1 firewall, you will need to set the entry_point config to point to one of your authenticators. Choose whichever you want, then see my note below about this.

    About your ultimate problem where the paths in access_control don't send you to the login page anymore, I'm not sure what's causing this. Ultimately, when the user hits an access_control path that requires login, it should trigger the "entry point" (i.e. the start method on your authenticator - whichever authenticator you have configured as your "entry_point"), which you'll configure to redirect the user to the correct login page. This will be a little tricky, because your one start method will need to be smart enough to figure out if you should redirect to /login/contact or /logic/account_manager. How you do this is up to your business logic - it might be that you look at what URL that *attempted* to go to, and redirect accordingly.

    Also, when you submit the login page (e.g. to /login/contact), that will call the getCredentials method on *every* authenticator that is under the active firewall. Remember: only *one* firewall is used on each request, and Symfony matches the firewalls from top to bottom until it finds ONE that matches. So, I'm guessing that with your original setup, the main firewall was being used, which has no authenticators. Put everything back under that one firewall, and you'll be good. Not counting the "dev" firewall, there are very few use-cases for multiple firewalls.

    Phew! So, that's a bit of work to do! Let me know if it helps!

  • 2017-01-16 weaverryan

    Hey Boran!

    This type of this is exactly why I created the Guard system - it should make this quite easy :). Here's what I would do: In getUser(), add your check for the "activated" field. If the user's account is not activated, do this:


    use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;

    // down in getUser()

    if (!$user->isActivated()) {
    throw new CustomUserMessageAuthenticationException(
    'Your have to activate your account first'
    );
    }

    And that should be it! Here's a reference about the customer message: http://symfony.com/doc/curr...

    Let me know if that helps! Cheers!

  • 2017-01-15 Vince Liem

    Hello, I'm trying to make multiple login forms for different kind of users. One for 'account managers' and one for 'contacts'.

    The account manager logs in with the usual email and password. The contact logs in with email and an access code given by account managers that changes every week.

    Well now I've made twice of everything. I have an accountmanager entity class and a contact entity class. Two forms (/login & /login/account_manager), also two login form authenticators, both registered as a service. I still have one securityController, but two actions. I hope that it was the right thing to do..

    I'm stuck in the security.yml, I've made two providers and in the firewall I have dev, main, contact and account_manager.

    The paths in access control doesn't automatically route to any login page anymore. If I submit the login page, it doesn't trigger any login form authenticator service.

    Basically I'm stuck :(. I was hoping that doing things twice would simply work.

  • 2017-01-14 Boran Alsaleh

    I'm using Symfony 3 Guard security system.

    When some user trying to login, I want to additionally check whether user's field "activated" is true. If not, error message appears: "You have to activate your account first".

    How can i implement this feature?

  • 2016-11-29 weaverryan

    I agree! This is definitely a slightly better way to do it, to use the security_login route instead of hardcoding /login</code).>

  • 2016-11-29 Lampje

    A tiny suggestion:

    Clean up getCredentials in LoginFormAuthenticator.
    Change:


    $isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod( 'POST' );

    into:


    $loginUrl = $this->router->generate('security_login');

    $isLoginSubmit = $request->getPathInfo() == $loginUrl && $request->isMethod( 'POST' );

    Makes it more consistent with the behavior in getLoginUrl and getDefaultSuccessRedirectUrl :-)
    Plus a bit more bulletproof, in case the Route changes.

  • 2016-10-30 Vince Liem

    Thank you !!

  • 2016-10-02 ugur ertas

    solved it thanks. I forgot to import the use statement in the LoginFormAuthenticator :p

  • 2016-09-30 weaverryan

    Definitely, in this case, using the user provider is fine. But, the user provider os famously mis-understood in Symfony. And from a teaching perspective, I wish it weren't included at all during the authentication process. The issue is that when things are simple (like in this case), a user provider makes perfect sense. But what if we added a 3rd box to our login - username, password and "student pin" (some sort of secondary password) or "company id". In that case, we would need to look up the user not just by username, but by username and another field. Historically, this *puzzles* people, because the user provider tends to make people think that they *must* use it, when in reality, they don't.

    tl;dr; I avoid the user provider so that the user will have a method that will work in *any* complex scenario. But, using it is totally fine.

    Cheers!

  • 2016-09-30 Victor Bocharsky

    Hey halifaxious ,

    That's a really good question! I think using user provider is more appropriate here, and we can avoid injecting other services here. So feel free to use user provider there, it solves this task much easy!

    Probably it was for training purposes. I think Ryan wants to show that we can inject whatever service we need, since `LoginFormAuthenticator` is just another service, like 'entity_manager', 'logger', and other well known services.

    Cheers!

  • 2016-09-29 halifaxious

    I'm a bit confused about why you're using an entityManager in `getUser()` instead of using the `$userProvider` that is passed in to the method. ie. why not write the method like this:

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
    $username = $credentials['_username'];
    $user = $userProvider->loadUserByUsername($username);
    return $user;
    }

  • 2016-09-26 weaverryan

    Hey there!

    One way or another, this error occurs when you try to load a form (LoginForm) through Symfony's form system, but the class (LoginForm) is not found! Make sure your LoginForm class lives in the "Form" directory and that you have a "use" statement for the LoginForm class *inside* your LoginFormAuthenticator class. One way or another, we have some missing use statement or namespace mis-match that is causing the issue.

    Let me know if that helps!

  • 2016-09-26 ugur ertas

    My LoginFormAuthenticator is in the Security folder but it still doesn't work I'm getting the same error as Andjii

  • 2016-09-25 weaverryan

    That's weird! Those should be identical - both mean "return null". So, I'm not sure why this happened! But I'm happy that it sounds like it's working now.

    Cheers!

  • 2016-09-24 diarselimi92

    I had the problem in the getCredentials because i returned 'return null; ' instead of ' return; ' and the page wasn't loading i think it was some kind of routing loop, i'm not sure but the page keep loading and it crashes.
    In case someone else have this problem :)

  • 2016-08-15 Lee Ravenberg

    I ran into the problem where the last_user value wasn't saved into the session. This was due to the logic that I adjusted.

    Tutorial logic:
    $isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod('POST');
    if (!$isLoginSubmit) {
    return;
    }

    My Logic:
    if (!$request->getPathInfo() == '/login' && !$request->isMethod('POST')) {
    return;
    }

    So I didn't see what went wrong and Ryan had a look at it. He pointed out that I *slightly* reversed the logic. Then he added:

    "For example, if you go to /login with a GET request (e.g. you submit the form, then symfony redirects you back to /login), I think it will skip your if statement. In this case, you call handleRequest(), but since there is no login information, $form->getData() is blank. This is then being set into the session, clearing out any LAST_USERNAME from the previous POST request. Later on the request, your controller is rendered and this is blank!"

    I think this helps anyone that run into the same issue :P

  • 2016-07-26 Andjii

    It works! Thank you so much!!=))

  • 2016-07-25 weaverryan

    Hi Andjii!

    Hmm. Can you double-check that your LoginForm class is in the Security directory? The most likely cause is some mis-match between your namespace and your directory - e.g. you're using the namespace Security, but you put LoginForm inside of the Form directory. If that's not the case, post your LoginForm and controller code - I think there is some *tiny* problem that is causing all this trouble :)

    Cheers!

  • 2016-07-25 Andjii

    got "Could not load type "AppBundle/Security/LoginForm" after 5:07.... I use Symfony 3.1. What may cause this error? I've done everything the same as you described, but got it...

  • 2016-07-25 Victor Bocharsky

    Hey Sean,

    Yes, you're right, it's deprecated since Symfony 3.1. Please, check Ryan's answer about it here:
    https://knpuniversity.com/s... .

    Cheers!

  • 2016-07-23 Sean Cooper

    protected function getDefaultSuccessRedirectUrl() doesn't seem to be available in the Symfony 3.1.2, am I missing something?

  • 2016-07-19 weaverryan

    Hey Matt!

    Yea, those are both really good topics :). Btw, for Facebook login, I use our OAuth2 bundle - https://github.com/knpunive... - which has a helper class + docs for using with Guard :).

    For FOSUserBundle, that's very interesting :) and we definitely need to cover it. Really, you could *completely* setup FOSUserBundle like normal (including with form_login under your firewall), and *then* add 1 or more authenticators if you needed some additional ways to login (beyond form_login). Or, you could use FOSUserBundle, but *not use form_login - and instead create your own form login authenticator. The important thing to remember is that FOSUserBundle has very little to do with security, it really just gives you (A) a User entity, (B) some routes/controllers for things like rendering the login form and registration and (C) a user provider (which *is* indeed one of the ingredients for any security system, including Guard).

    I hope that helps in the short term! Cheers!

  • 2016-07-16 Matt W.

    Hi Ryan, Thanks for the reply. I didn't have any specific questions about Guard. I was just curious to learn more, in general, about it. Some of the chapter topics in the Guard tutorial seemed interesting, such as Facebook login and FOSUserBundle.

    I will keep my eyes open for the updated content for the new Guard component in the future. Thank you again.

  • 2016-07-16 weaverryan

    Ah yes - that's my fault :). The current "Guard" tutorial actually covers the older GuardBundle that "inspired" the Guard component in Symfony. We have plans to update that to help people go deeper with the Guard component. This is a rare spot where - at least currently - we didn't add any videos to this series, as we meant for it to be a kind of "open source" documentation about that library.

    In the future, we will at least update that to cover the new Guard component (and we may do that with normal video content). But, for the time being - is there some question about Guard we can help with?

    Cheers!

  • 2016-07-15 Matt W.

    Hi Victor. Some of the chapters, in the linked Guard tutorial, say 'coming soon'. None of chapters have videos attached to them. Although, maybe they were never supposed to have videos.

  • 2016-07-15 Victor Bocharsky

    Hey Matt,

    What do you think isn't finished/updated?
    We update our tutorials from time to time or sometimes just add some notes about different behaviours for newer versions. If changes too much for update - we start another new "v2" tutorial like behat 2.5 and behat 3 courses.

    Cheers!

  • 2016-07-14 Matt W.

    Hi thanks for the great series. Do you know if there are any plans to go back and finish/update the linked Guard tutorials?