Buy

Authenticator: getUser, checkCredentials & Success/Failure

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

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

... 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']:

... 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:

... 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:

... 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:

... 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():

... 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']:

... 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:

... 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:

... 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:

... 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:

... 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'):

... 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():

... 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'):

... 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:

... 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:

... 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:

... 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!

  • 2018-06-22 weaverryan

    Hey Alexandros Spylitopoulos!

    Bah! Good fix! That was my fault - I completely missed the fact that you had two firewalls! And yes, I like your solution: you rarely need 2 real firewalls.

    Thanks for updating me on the fix!

  • 2018-06-22 Alexandros Spylitopoulos

    Hey weaverryan ,

    I solved the problem. I didn't need to follow any of the steps provided.
    As it seems the problem was most probably due to having two firewalls set up instead of one.

    If you check I had the login and the admin firewall so that mean two provider keys. The two provider keys means two different tokens ( check createAuthenticatedToken(UserInterface $user, $providerKey) at GuardAuthenticatorInterface ) and two different session variables. So when I was logged in the first time my request was routing via login firewall ($providerKey) it was creating a token that was then stored in session under key _security_login ( session key pattern is _security_$providerKey). The second time my request was routing through the admin firewall ($providerKey) and was try to find in the session key _security_admin which was empty and thus failing to get the serialized token that contained the User object.

    What I did was to creating one firewall and then use the access_control correctly as I had to do from the beginning I suppose :P nevertheless thank you so much for your support!

  • 2018-06-19 weaverryan

    Hey Alexandros Spylitopoulos!

    Hmm, this is indeed very strange! It's very strange that after the first redirect, you are not authenticated (not user) and then after the second redirect, you are authenticated. Basically, something *very* strange is happening, and I would recommend removing as much code as you can to try to find the problem. A few questions:

    A) Did you try to implement EquatableInterface on your User and return true from isEqualTo()? I don't think this will fix the problem, but we need to try it to be sure.

    B) Do you have just 1 authenticator or other authenticators?

    C) Can you post your security.yml file and authenticator code?

    Cheers!

  • 2018-06-18 Alexandros Spylitopoulos

    Hi Ryan,

    First of all thank you for you reply!
    Secondly I did the test that you asked and I also did the whole cycle noticing the security getUser() value. There are happening 3 redirects before accessing Security Controller and loginAction. First is the credentials post, here $this->_security->getUser() is NULL so authentication goes on. On authentication success I should be redirected to WelcomeController again authenticator starts and the $this->_security->getUser() IS AGAIN NULL so I am redirecting back to LoginController. Now in authenticator this time the $this->_security->getUser() has the authenticated user object so no authentication takes place and I am redirecting straight to my LoginController. Here I used the same service as in my authenticator $this->get('security.helper')->getUser() and the user object exists! However due to my session logic I redirect back to WelcomeController. I am again inside authenticator and guess what...? $this->_security->getUser() is NULL AGAIN so I am redirecting back to LoginController to see that $this->get('security.helper')->getUser() has the user. I hope I didn't make you feel dizzy :)

    So as you see it seems like something is writing and deleting the security getUser? Also there isn't any serialize() method on my User Class. If you need to provide any extra code please let me know!

    Thanks again for your help

  • 2018-06-18 weaverryan

    Hey Alexandros Spylitopoulos!

    Apologies for my slow reply! So.... hmm, this is a mystery! First, yes, $this->_security->getUser() should return the User object once your User is authenticated. The idea behind your code here is correct! So, here is my guess: after you redirect, you are somehow *losing* authentication. This is actually something that *can* happen if your code isn't setup quite correctly. Basically, you authenticate, redirect, but then there is a problem reloading your User object from the session.

    Here is how you can see if this is your problem. In loginAction, instead of checking for $session = $this->get('session');, do the *same* check that you have in your authenticator - get the security service and call getUser(). If I am correct, you will find that you are being *immediately* logged out, and so, this will now render the login form.

    IF this is your problem, let me know: the issue is with the serialization of your User object. If you have a serialize() method on your User class, that could be the problem. The temporary fix would be to implement an EquatableInterface on your user, which requires you to add a new isEqualTo(UserInterface $user method. Try returning true from this. If it fixes your problem, then my guess is right. Let me know, and then we can talk about the correct solution.

    But, if I'm totally wrong, also let me know!

    Cheers!

  • 2018-06-13 Alexandros Spylitopoulos

    I have a problem ERR_TOO_MANY_REDIRECTS in Chrome. I am using Symfony 3.4.10.

    What is happening is that when I log in for the first time using POST, my authenticator guard in the $this->_security->getUser() (using the Supports method). is NULL which is correct and it authenticates and redirects to WelcomeController (using the onAuthenticationSuccess method). Up until here it's working fine. Upon redirection in the authenticator guard the $this->_security->getUser() is NULL AGAIN even though I have already logged in and thus it authenticates again which causes to go to LoginController (using the getLoginUrl method) where here again just because I have a logic that if session variable userId exists it should be redirected to WelcomeController. This goes forever.

    Shouldn't the value $this->_security->getUser() be other than null upon my second redirection since I have already log in?

    Here is the code below.

    In my authenticator guard the first function.


    public function supports(Request $request)
    {
    if ($this->_security->getUser()) {
    return false;
    }
    return true;
    }

    in my SecurityController.php


    public function loginAction()
    {
    $session = $this->get('session');
    if ($session->has('user_id')) {
    return $this->redirectToRoute('admin_welcome');
    } else {
    $authenticationUtils = $this->get('security.authentication_utils');
    // get the login error if there is one
    $error = $authenticationUtils->getLastAuthenticationError();
    // last username entered by the user
    $lastUsername = $authenticationUtils->getLastUsername();
    $user = new \TB_Models_User();
    $user->setUsername($lastUsername);
    $userForm = $this->createForm(UserType::class, $user);
    $userForm->remove('_plainPassword');
    }
    return $this->render('admin/login/index.html.twig',[
    'userForm' => $userForm->createView(),
    'error' => $error,
    ]);
    }

    and security.yml


    login:
    pattern: ^/admin/login$
    security: true
    anonymous: true
    provider: admin_tb_user_provider
    stateless: false
    guard:
    authenticators:
    - app.admin_authenticator

    admin:
    pattern: ^/admin
    provider: admin_tb_user_provider
    security: true
    stateless: false
    guard:
    authenticators:
    - app.admin_authenticator

    access_control:
    - { path: ^/admin/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/admin, roles: [ROLE_USER] }


    Any advice?

  • 2018-02-21 Victor Bocharsky

    Hey Mohamed,

    I think I know the reason, look at the 3rd argument which is passed to PostAuthenticationGuardToken: https://github.com/symfony/... . It means your users::getRoles() always should return an array. Well, you can return an empty array if user does not have a role, but would be better to always return at least one role for user. You can look at implementation of User::getRoles() by FOSUserBundle: https://github.com/FriendsO...

    Cheers!

  • 2018-02-21 Mohamed Sahil

    When my authentication is successful why i am getting this error

    Type error: Argument
    3 passed to
    Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken::__construct()
    must be of the type array, null given, called in
    /home/sahil-pc/project/MessageApp/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php
    on line 38

    In all other cases i m getting results that i expect but when my authentication is successful i am getting this error.

    thank u

  • 2017-12-12 Diego Aguiar

    Hey Daka!

    Did you fix all you problems? I got a bit lost in all those comments :)

    Cheers!

  • 2017-12-12 Daka

    I think there is something with use but cant figure out :(

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

  • 2017-12-12 Daka

    FatalThrowableError in LoginFormAuthenticator.php line 32:
    Type error: Too few arguments to function AppBundle\Security\LoginFormAuthenticator::__construct(), 0 passed in /Users/a12345/panel/v8/symfony-security/start/var/cache/dev/appDevDebugProjectContainer.php on line 381 and exactly 3 expected

    all here:
    /**
    * @var FormFactoryInterface
    */
    private $formFactory;
    /**
    * @var EntityManager
    */
    private $em;
    /**
    * @var RouterInterface
    */
    private $router;

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

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

    what should be a problem?

  • 2017-12-12 Daka

    fixed by adding use Doctrine\ORM\EntityManager

  • 2017-12-12 Daka

    Error seems to be: AppBundle\Security\EntityManager and it is not there, maybe I forgot to include the class?

    RuntimeException in AutowirePass.php line 155:
    Cannot autowire argument 2 for AppBundle\Security\LoginFormAuthenticator because the type-hinted class does not exist (Class AppBundle\Security\EntityManager does not exist).
    in AutowirePass.php line 155
    at AutowirePass->completeDefinition('app.security.login_form_authenticator', object(Definition)) in AutowirePass.php line 45
    at AutowirePass->process(object(ContainerBuilder)) in Compiler.php line 104
    at Compiler->compile(object(ContainerBuilder)) in ContainerBuilder.php line 528
    at ContainerBuilder->compile() in Kernel.php line 477
    at Kernel->initializeContainer() in Kernel.php line 117
    at Kernel->boot() in Kernel.php line 166
    at Kernel->handle(object(Request)) in app_dev.php line 30
    at require('/Users/a12345/panel/v8/symfony-security/start/web/app_dev.php') in router_dev.php line 40

  • 2017-11-22 Javier

    Hello guys,

    I'm facing issues when trying to implement CSRF protection with Guard.

    I've followed your tutorials to create a form that flawlessly works but then I tried to add CSRF according to the official docs and now my login page isn't loading.

    I've commented out CSRF on config.yml and done the following to my security.yml



    security:

    always_authenticate_before_granting: true

    encoders:
    AppBundle\Entity\User: bcrypt

    providers:
    our_users:
    entity: { class: AppBundle\Entity\User, property: email }

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

    main:
    anonymous: ~
    guard:
    authenticators:
    - AppBundle\Security\LoginFormAuthenticator

    logout:
    path: /logout
    target: /
    invalidate_session: true

    form_login:
    login_path: /login
    check_path: /login
    csrf_token_generator: security.csrf.token_manager

    Then, in LoginFormAuthenticator.php I've added the following to the method getCredentials():


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

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

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

    $form = $this->formFactory->create(LoginFormType::class);
    $form->handleRequest($request);
    $data = $form->getData();

    $request->getSession()->set(
    Security::LAST_USERNAME,
    $data['_username']
    );

    return $data;
    }

    What am I doing wrong? Am I missing something obvious?

    Thanks!

  • 2017-11-20 weaverryan

    Hey @gogosacco!

    Yep, the new configuration that we introduced in Symfony 3.3 is quite different :). So I agree completely: we recommend using Symfony 3.1 if you want to code with the tutorial. Then everything will work great and you can focus on the topics! Then, check out https://knpuniversity.com/s... to learn all about the Symfony 3.3 stuff... which btw, is *really* cool and fun stuff.

    Good luck!

  • 2017-11-20 Gogo Sacco

    the problem arose because the current symfony version 3.3.* was installed! so far it was hardly a problem. but after this tutorial I could not solve the problem described above, although the two configuration files were in fact the same and had no typos. In the current symfony version, the configuration seems to work quite differently. I'm going to use 3.1 for the rest of the course, which I really like. I will then try to upgrade the app to 3.3 later. Thank you for your quick response.

  • 2017-11-20 weaverryan

    Hey @gogosacco!

    Oh no! Ok, let's see if we can debug. You already downloaded the course code and tried that... which is very weird (that is indeed the exact code we use for recording the tutorial). So, let's debug!

    A) The getCredentials() method should be called on *every* request. If you put a die(); statement in that method and refresh, does it hit the die or not? It sounds like it is never called, but I want to be sure :).

    B) If getCredentials() is never called, then I can say that *definitely* the issue is somewhere in security.yml or services.yml. You said you're using the course code exactly, but can you post these files anyways just to be sure?

    C) In your config_dev.yml file, if you temporarily set intercept_redirects to true (https://github.com/symfony/..., then Symfony will stop before redirecting you. This can be useful because, it's possible that when you submit the login form, more is happening than you realize (in theory, your authenticator could be working, redirecting to the homepage, but then you're losing authentication for some reason, and so you are redirected back to /login). If there are any surprise redirects happening, this would show you.

    Let me know what you find out!

    Cheers!

  • 2017-11-19 Gogo Sacco

    I'm facing the exact same problem as Jelle Schouwstra! Only that my service.yml and security.yml are exactly the same as the ones in the tutorial. no typos. nevertheless getCredentials () is not called, also getUser () is not called. It just loads the login form again after entering the credentials. Of course I have the downloaded tutorial version installed. I just installed again with the finish version and changed nothing. It causes the exact same issue. Can someone help me with this?

  • 2017-11-02 Jonathan Gratton

    Solved.

    I had commented out some of the services.yml as the tutorial pointed out. I'll just have to deal with the deprecation now.

    Thank you!

  • 2017-11-02 Diego Aguiar

    Hey Jonathan Gratton

    Look's like you are using the new features of Symfony 3.3, how are you executing that login action? Symfony by default should inject those arguments

    Cheers!

  • 2017-11-02 Jonathan Gratton

    I am following along but encounter the following error around this point in the tutorial:

    "Controller "AppBundle\Controller\SecurityController::loginAction()" requires that you provide a value for the "$authUtils" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one."

    Help?

    Symfony 3.3.9

       public function loginAction(Request $request, AuthenticationUtils $authUtils)
    {
    // get the login error if there is one
    $error = $authUtils->getLastAuthenticationError();

    // last username entered by the user
    $lastUsername = $authUtils->getLastUsername();

    $form = $this->createForm('AppBundle\Form\LoginForm', [
    '_username' => $lastUsername
    ]);

    return $this->render('security/login.html.twig',
    array(
    'form' => $form->createView(),
    'error' => $error,
    ));
    }
  • 2017-10-31 Victor Bocharsky

    Hey Javier,

    No problem! I'm glad you got it working ;)

    Cheers!

  • 2017-10-30 Javier

    Sorry, it was my bad. I said extends when I meant to say implements. It's all working now. Thanks.

  • 2017-10-27 Victor Bocharsky

    Sorry to mislead you :) You wrote "my User entity extends UserInterface" and I thought you have an interface which extends base UserInterface, so I mean to use *your* interface instead of "ExtendedUserInterface" I wrote.

    In short, just replace my ExtendedUserInterface with your User entity and it will work ;)

    Cheers!

  • 2017-10-27 Javier

    Thanks for the prompt reply Victor.

    I don't seem to have access to ExtendedUserInterface, would my code look something like this?


    public function checkCredentials($credentials, UserInterface $user)
    {
    if (!$user instanceof User) {
    throw new \InvalidArgumentException('Object of ExtendedUserInterface was expected in checkCredentials() method -
    how does it possible?!');
    }

    $password = $credentials['_password'];

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

    if ($user->isGenuine()) {
    return true;
    }

    return false;
    }

  • 2017-10-27 Victor Bocharsky

    Hey Javier,

    Ah, I see what you mean, declaration of the overridden method should be compatible with parent class. That's a problem if you trying to change UserInterface typehint to a different interface, but you can keep ti as is and just to imply that in this place you get an object which implements your own interface, and it should work fine. But to be 100% sure, you can easily use a workaround for it:


    /**
    * @param ExtendedUserInterface $user
    */
    public function checkCredentials($credentials, UserInterface $user)
    {
    if (!$user instanceof ExtendedUserInterface) {
    throw new \InvalidArgumentException('Object of ExtendedUserInterface was expected in checkCredentials() method -
    how does it possible?!');
    }

    $password = $credentials['_password'];
    if ($this->passwordEncoder->isPasswordValid($user, $password))
    {
    return true;
    }

    // Actually, "is_active" means a bit different, I'd name this property "is_fake_user", and then:
    if ($user->isFakeUser()) {
    return true;
    }

    return false;
    }

    Then, in PhpStorm you'll get autocompletion due to instanceof check automatically, or you just can add @param PHP annotation as I did for this method.

    Cheers!

  • 2017-10-27 Javier

    Hello,

    I got all this working successfully, but I'm trying to do additional checks on the user and I'm not sure how to proceed.

    The method I'm interested in is

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

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

    return false;
    }

    In here, I'd like to check if the user is active or not (my User entity extends UserInterface and has a boolean property called is_active). How can I achieve it? I can't use User $user because it's not compatible with the method declaration.

    The reason why I'm trying to implement this check is because I get loads of fake accounts registering for my app.

    Thanks,

  • 2017-08-17 Diego Aguiar

    Hey Mike

    We like to follow the convention of "Don't optimize prematurely", after you implement something, give it a time to really know if it's having slow performance, or use profiling tools like BlackFire.

    I believe doing it via a listener will consume a few more cycles to your processor (because it has to execute a few more lines of code), but nothing to be worry about.

    Cheers!

  • 2017-08-17 Mike

    Awesome tutorial! One question, doesn't it affect performance to log it via the Event Subscriber (New file, new service) instead inside of the "onAuthenticationSuccess()" Method?

    In other videos you mentioned that some things get "compiled" in the cache folder, does this as well and due to that fact doesn't impact performance?

    In my mind I constantly ask the question of performance vs readability and readability *for me* is granted for both ways, so I would choose the most performant way.

  • 2017-08-17 Hugo Le Goff

    Thank you again for all these good advices, It's working pretty well right now !

    I just have to integrate the "remember me" and I'm done. I'll keep following your tutorials and maybe I'll come back to you for more help.

    Have a nice day (I know it's early morning :D)

  • 2017-08-17 weaverryan

    Hey Hugo Le Goff!

    I'm so happy you're learning Symfony quickly! That's awesome! And up at 3am !? You are dedicated :).

    I like that you want to put your API logic into a service. Nice job! To get access to the service from within your authenticator, you need to use classic dependency injection. I mean, don't add ServiceObject $service as an argument to getCredentials(). Instead, add another argument to your __construct method and pass it in like any other dependency. Thanks to autowiring, if you type-hint your argument with your class name, Symfony will automatically pass it to you. This is always what you should do: dependencies are passed through the *constructor* - not to method calls.

    In other words, you're VERY close :).

    Good night and cheers!

  • 2017-08-17 Hugo Le Goff

    You've got it !

    Thank you very much ! I guess you used your time on these exemples, and that's really cool.

    Keep making awesome tutorials on Symfony, I'm learning it quite faster thanks to yours.

    If I can ask a last think, I prefer to use a service to call the API with all the functions inside. So I tryed toi add the service I maked on the getCredentials function parameters (I don't know if it's the right name, I'm talking about that : getCredentials (ServiceObject $service) ) but PhpStorm told me that it's not possible due to the parent class of the listener wich define only $credentials and $userProvider as parameters. So I'm asking if there is a way to use that service on getCredentials. If it's too hard to realize like changing the parent class and then Symfony itself, forget I'll just add the same code of my service. But if I can keep a clean code it would be great.

    Again thank you for all the help you gave me, now I wanna sleep beacause it's already 3:00 a.m. in France. :D

  • 2017-08-16 weaverryan

    Yo @hugolegoff!

    Haha, now I think I understand. When your users "login" to your website, you actually make an API request to ANOTHER site to authenticate those users (like an SSO). I misunderstood at first! Here's what I would do:

    A) In getCredentials(), return whatever information the user us sending on the request. So, if your user is using a normal login form (with email address & password), then return these from getCredentials(). This would look very similar to what we do in this chapter.

    B) In getUser()... do everything else ;). For example:


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

    // make an API request to your external authentication endpoint

    // this is a pretend variable - somehow if the user sent a bad email or password, then your
    // authentication API will somehow tell you it failed
    $authFailed = '...';
    if ($authFailed) {
    // fail authentication
    return null;
    // or throw this for more control over the message
    // throw new CustomUserMessageAuthenticationException('Invalid email or password');
    }

    // at this point, your know the user is valid - because your API told you it is. Yay!
    // that API should return data about your user... mostly importantly it should return
    // some uuid that never changes. We will have a uuid property in the user

    $user = $this->em->getRepository('AppBundle:User')
    ->findOneBy(['uui' => $uuidFromApi]);

    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);
    $user->setUuid($uuid);
    // set any other information... that you may have gotten from your API
    $this->em->persist($user);
    $this->em->flush();

    return $user;
    }

    How does that sound? :)

  • 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 @disqus_i3r0u5EGoU!

    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?