Buy

Automatically Login after Registration!

If I submitted this form right now, it would register me, but it would not actually log me in... which is lame. Let's fix that.

This is always pretty easy, but it's especially easy because we're using Guard authentication. Inside of UserController, instead of redirecting to the home page: do this: return $this->get() to find a service called security.authentication.guard_handler. It has a method on it called authenticateUserAndHandleSuccess(). I'll clear the arguments and use multiple lines:

44 lines src/AppBundle/Controller/UserController.php
... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
... lines 22 - 29
return $this->get('security.authentication.guard_handler')
->authenticateUserAndHandleSuccess(
... lines 32 - 35
);
}
... lines 38 - 41
}
}

The first argument is the $user and the second is $request:

44 lines src/AppBundle/Controller/UserController.php
... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
... lines 22 - 29
return $this->get('security.authentication.guard_handler')
->authenticateUserAndHandleSuccess(
$user,
$request,
... lines 34 - 35
);
}
... lines 38 - 41
}
}

The third argument is the authenticator whose success behavior we want to mimic. Open up service.yml and copy the service name for our authenticator:

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

In the controller, use $this->get() and paste the service ID:

44 lines src/AppBundle/Controller/UserController.php
... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
... lines 22 - 29
return $this->get('security.authentication.guard_handler')
->authenticateUserAndHandleSuccess(
$user,
$request,
$this->get('app.security.login_form_authenticator'),
... line 35
);
}
... lines 38 - 41
}
}

Finally, the last argument is something called a "provider key". You'll see that occasionally - it's a fancy term for the name of your firewall:

40 lines app/config/security.yml
... lines 1 - 2
security:
... lines 4 - 14
firewalls:
... lines 16 - 20
main:
anonymous: ~
guard:
authenticators:
- app.security.login_form_authenticator
... lines 26 - 40

This name is almost never important, but it actually is in this case. We'll say main:

44 lines src/AppBundle/Controller/UserController.php
... lines 1 - 10
class UserController extends Controller
{
... lines 13 - 15
public function registerAction(Request $request)
{
... lines 18 - 20
if ($form->isValid()) {
... lines 22 - 29
return $this->get('security.authentication.guard_handler')
->authenticateUserAndHandleSuccess(
$user,
$request,
$this->get('app.security.login_form_authenticator'),
'main'
);
}
... lines 38 - 41
}
}

And we're done!

The Bonus Superpower

Now, this will log us in, but it also has a bonus super-power. Right now, we're anonymous. So let's try to go to /admin/genus. Of course, it bounces us to the login page. That's fine - click to register. Use weaverryan+20@gmail.com, add a password and hit enter.

Check this out. Well, ignore the access denied screen.

First, it did log us in. And second, it redirected us back to the URL we were trying to visit before we were sent to the login page. This is really great because it means that if your user tries to access a secure page - like your checkout form - they'll end up back on the checkout form after registration. Then they can keep buying your cool stuff.

Of course, we see the access denied page because this user only has ROLE_USER and the section requires ROLE_MANAGE_GENUS.

Signing Off

Ok guys, that's it. Yes, you can get more complicated with security, especially authentication. And if you need to check permissions that are object specific - like I can edit only genuses that I created - then check out Symfony's awesome voter system. Hey, we have a course on it!

But for the most part, you guys have the tools to do really incredible things. So go out there, build something awesome and tell me about it.

Seeya next time!

Leave a comment!

  • 2017-11-07 weaverryan

    Hey Ricard!

    This is a GREAT use-case for a Guard authenticator :). Let me try to give you some guidance. Basically, for simplicity, I would probably put all of the logic into getUser(). Something like this:


    class LdapAuthenticator extends AbstractGuardAuthenticator
    {
    public function getCredentials(Request $request)
    {
    return [
    'username' => $request->request->get('_username'),
    'password' => $request->request->get('_password'),
    ];
    }

    public function getUser($credentials, UserProviderInterface $userProvider)
    {
    $user = '...'; // check if the user exists in MySQL
    if (!$user) {
    // this will fail authentication
    return;
    }

    $userExistsInLdapMode = '...'; // add the check for "if the user exist in the LDAP mode"
    if ($userExistsInLdapMode) {
    // look up the user in LDAP. If it fails
    // throw a new CustomUserMessageAuthenticationException('Invalid username/password combination');
    // if you want to control the authentication message

    // if the password IS valid, return the user object
    return $user;
    } else {
    // in this case, you just want to check the user's password like normal
    // usually, you would do that in getPassword(). But let's keep it simple
    // and do the password check here. If the password is incorrect, then
    // throw a new CustomUserMessageAuthenticationException('Invalid password');

    // if the password IS valid, return the user object
    return $user;
    }
    }

    public function checkCredentials($credentials, UserInterface $user)
    {
    // since *all* of the password checking was done in getUser()
    // it's safe to just return true here
    return true;
    }

    // ...
    }

    Let me know if that helps!

  • 2017-11-07 Ricard Espinàs

    Hello friends of KNP.

    What I'm pretending to do is to include the LDAP for internal users in a Guard Authentication System configured by ddbb.
    I already have build my Guard Authentication System and works really nice thanks to https://knpuniversity.com/s....

    But I need also to try to log in previously via LDAP mode. More precisely, the functionality must be like this:

    The user try to log in on the Guard System Authentication configured with a database from MySQL and:

    1- Check if exist the user in the table User from MySQL. If exist, we
    go to step 2. If not exist return false to the authentication with the
    error message.

    2-Check if the user exist in the LDAP mode. If exist go to the step 3. If not exist go to the step 4.

    3-Try to log in via LDAP with the username and password. If the
    authentication is ok, it's logged in. If can't match the password via
    LDAP, return false to the authentication with the error message.

    4-After checking the LDAP option, we will just try to log in via
    Guard Authentication System. If the authentication it's ok, the user is
    logged in. If can't match the password via Guard with the MySQL users
    table, return false to the authentication with the error message.

    Could anyone help me?

  • 2017-07-13 Xav

    Thank you again for your time :)
    So I put back my two routes to "/login" and "/register". Neither Authentication nor Registration work on home page. But if I use Ajax with $('form[name="login"].serialize()) and send it to the routes, both work and it's fine for me. I just have to handle success now.

  • 2017-07-13 weaverryan

    Hey Xav!

    If I understand you correctly, you have your login and registration forms on your homepage. And, the login form works but the registration form does not work. Is that correct?

    Where do you have the registration form submit handling code (the $form->handleRequest($request) stuff)? Do you have this in the controller that renders the homepage? Or does it live in a different controller - e.g. a controller for the /register page? If the registration-handling code lives at /register, then what you need to do is make sure that your form action attribute points to /register. In other words, right now, when you submit, it is submitting back to your homepage, and is never hitting your form logic. If this is your problem, add this to your form:


    {{ form_start(form, { 'action': path('security_registration' }) }}

    Change security_registration to your route name.

    If this is *not* your problem, let me know!

    Cheers!

  • 2017-07-13 Xav

    Hi !
    Thanks to Knp I now have a fully functional login and registration system through Guard. BUT...But now I have an issue. I want to have both on the home page (and a facebook button, but it works fine). With twig render I can have both forms. In my routing file, I set the paths to "/" (and in my LoginFormAithenticator -> getCredentials too). Login works, but not register (it reloads home). If I set the register path back to "/register" it works fine, but not on the home page.
    What can I do to solve this ?
    Thank you.

    Edit : I thought using Ajax could solve my problem (and be prettier) so I searched on your site and found this ressource https://knpuniversity.com/s... but I have troubles to achieve the very first sentence "Suppose your login form uses AJAX." Is there any article on that part ?

  • 2017-04-17 weaverryan

    Hey Giorgio Pagnoni !

    Ah, fascinating! I've never run into this and I don't see the refreshUser() in FOSUserBundle anymore (though arguablly, it should be there still: https://github.com/FriendsO....

    But, it does make sense. One of the side effects of binding your User object to your profile form is that when you submit and have a validation error, your User object *has* been modified. Those modifications haven't been saved to the database, but as that one page builds, your security system uses the modified User object (e.g. if you update the username and you print the username on the page - like in the header - you'll temporarily see the new, updated username in that space).

    About it logging you out, I can't repeat it locally right now, but I suspect I know the cause. At the end of each request, your User object is serialized into the session. Then, at the beginning of the next request, it's unserialized (and then reloaded from the database, to make sure it's fresh). When you submit the page with a validation error, it actually serializes the updated/invalid User to the session. In practice, that's not a problem because when the User object is deserialized, Symfony uses the "id" from that User to query for a fresh User (so even if some of the info on the serialized User object is "wrong", a fresh User is queried for anyways). However, when it does this process, it compares the serialized object with the fresh object in the database. If certain things have changed (e.g. the username or password), then the User is said to have "changed" and you're logged out for security reasons. I suspect that this is happening in your case. By default (unless your User implements EquatableInterface), if your username, password or salt fields changed, then your user looks like it "changed" (if you User implements AdvancedUserInterface, which I don't typically recommend, then a few other things are checked).

    By calling $em->refresh() if the form is not valid, you're reloading the User from the database so that the fresh, unedited User is the one that's serialized in the Session. In practice, this is probably a good idea - if it doesn't mess anything up in your form, I'd stick with it :).

    Cheers!

  • 2017-04-12 Giorgio Pagnoni

    Awesome content as usual. As a follow-up to this course, I created an editAction in my controller to allow my users to modify their profile, but here's the thing: if the form validation fails once and then in the following step goes through, the profile is updated but the user gets logged out. I fixed it by doing this http://stackoverflow.com/qu... (so basically by adding $em->refresh($user) if the form is not valid) but I don't really understand what's going on. Hope it's going to help someone!

  • 2017-02-23 weaverryan

    Good debugging! Thanks for sharing!

  • 2017-02-23 Blueblazer172

    Okay i made a silly mistake;)
    I didn't open the connection from my MySQL server.

    So what i did I changed the bindadress in the config file (btw I'm using Maria DB and only this one is a bit different than others) to 0.0.0.0 so that I could be attached to any ip.

    Maybe this helped others with the same problem. Now everything is working;)

  • 2017-02-22 weaverryan

    Perfect! When you find a solution, it might help others!

  • 2017-02-22 Blueblazer172

    i'm going to try this one and let you know if it worked and maybe what i've done to fix it :)

  • 2017-02-22 weaverryan

    Hey Blueblazer172!

    1) Yea, something is definitely wrong with the database configuration... the question is what is the problem!? I would recommend trying to connect to the database from your host machine via some other method to check the credentials. For example, you might use the command line to try to connect - e.g. mysql -u root -h 192.168.1.100 (obviously, use your real IP address!). Or, you could use something like Navicat or Sequel Pro. I imagine that you will *also* get a "connection refused" error when you try this. But then, it may be easier to debug! I *do* think that port forwarding might be a problem, or the port just might not be open on your virtual machine: "Connection Refused" means that the server completely rejected the connection - you get this exact error when you try to connect to a machine on a port that is not open.

    2) Once you solve issue (1), try running bin/console assets:install again. Right now, the database problem is blocking this from completing. But once that works, this command will work and BOOM, you will have the nice error styling you deserver.

    And sorry for my late reply! Good luck and cheers!

  • 2017-02-17 Blueblazer172

    1) i double checked my connection and if my db server is running and also my parameters file but still the same error. Btw the db server is running on another virtual machine so it is not installed with the localhost. i changed the ip and login credentials to my needs to that server, but it always throws me the same error...
    could there be something wrong with prot worwarding? but both machines are running in the same network and all ports are open in my network...

    2) i still dont get the nice style from symfony. i cleared the cache in dev and in prod it throws me an error with connection refused 2002 SQLSTATE. So i think there must be something wrong with the db connection...
    after clearing the cache i still get the same output as in the picture i recently uploaded.

  • 2017-02-17 Victor Bocharsky

    Hey Blueblazer172 ,

    1) Please, double check your DB credentials in `app/config/parameters.yml`. If this file doesn't exist - execute "$ composer install" in your terminal which creates it for you and then fill it with right credentials.

    2) I believe executing "$ composer install" without errors fix this styles. Actually, composer run another Symfony command behind the scene: "$ bin/console assets:install" which install some Symfony built-in CSS styles to make it looks prettier. However, if you got some errors during the composer install, this command was omitted. So you should run this command manually and it'll fix the styles. Also, try to clear the cache.

    Cheers!

  • 2017-02-17 Blueblazer172

    1. Why am I getting a error with the db connection refused ?
    2. Why is the symfony dev page looking so wierd?

    a picture of the error and the dev page
    http://imgur.com/a/R8hli

  • 2016-12-22 Victor Bocharsky

    Hi Boran,

    Yes, it is! Check the knpuniversity/oauth2-client-bundle. Actually, with this bundle you can do social networks authentication/authorization with or without FOSUserBundle. But if don't want to use FOSUserBundle - you will have to implement User by yourself, i.e. implement UserInterface.

    Cheers!

  • 2016-12-22 Boran Alsaleh

    Hallo ,how i can make facebook google twitter login authentication and authorization with out using FOS Bundle , Is there a special bundle for doing that

  • 2016-12-12 Boran Alsaleh

    Thank you very much :)

  • 2016-12-12 Victor Bocharsky

    Hey Boran,

    We're planning to add the level of difficulty for each course, but while we haven't added it yet - here's my rough gradation for these courses:
    -Joyful Development with Symfony 3 - beginner
    -Symfony Fundamentals: Bundles, Configuration & Environments - beginner
    -Symfony: Doctrine & the Database - intermediate
    -Mastering Doctrine Relationships in Symfony - advanced
    -Symfony: Level up with Services and the Container - intermediate
    -Symfony Forms: Build, Render & Conquer! - intermediate
    -Symfony Security: Beautiful Authentication, Powerful Authorization - advanced

    But I think you can get a rough estimate by yourself: the easiest courses have beginner level, the hardest courses have advanced ;)

    Cheers!

  • 2016-12-12 Boran Alsaleh

    Hi ,

    I have completed the below courses :
    I would like to know what is the level of those courses (beginner, intermediate or advanced ), thanks :)
    -Joyful Development with Symfony 3
    -Symfony Fundamentals: Bundles, Configuration & Environments
    -Symfony: Doctrine & the Database
    -Mastering Doctrine Relationships in Symfony
    -Symfony: Level up with Services and the Container
    -Symfony Forms: Build, Render & Conquer!
    -Symfony Security: Beautiful Authentication, Powerful Authorization

  • 2016-12-07 Victor Bocharsky

    Ah, your latest error makes sense... What about your User::$roles property declaration? It should be like this:


    class User {
    private $roles = [];
    }

    I suppose it'll also fix your first error too ;)

    P.S. Please, double check you don't set any non-array value to the $this->roles in the User::_construct() method or in any other method.

    Cheers!

  • 2016-12-07 Christophe Lablancherie

    Hi ryan ^^,

    Well i think it's that too, but my GetRoles() return an array...

    public function setRoles(array $roles)
    {
    $this->roles = $roles;
    }
    public function getRoles()
    {
    $roles = $this->roles;

    if(!in_array('ROLE_USER',$roles)){
    $roles[] = 'ROLE_USER';
    }

    return $roles;
    }

    And with that, i can logging normally (not automatically) but i can't register because i raise an exception : Warning: in_array() expects parameter 2 to be array, null given -_-

    I think i will stop coding and going back to school to learn how paint the wall. T_T

  • 2016-12-07 weaverryan

    Hey guys!

    I believe the problem is in the getRoles() function in your User class - it looks like that method is returning a string instead of an array (that's the cause of the error). Double-check that method and make sure it's *always* returning an array, even if it's an array with just one item :).

    Cheers!

  • 2016-12-07 Christophe Lablancherie

    Hi Victor,

    Sorry i've said 2.8 but i'm on 2.8.14 in reality, and yes it's a weird error, i don't understand what happen

  • 2016-12-07 Victor Bocharsky

    Hey Christophe,

    It's weird error. Could you try to update Symfony to the latest 2.8 version, it's 2.8.14 for now? Most likely this error should be gone. Let me know if it helps.

    Cheers!

  • 2016-12-06 Christophe Lablancherie

    Hi, i have one little problem.

    I've written this :
    return $this->get('security.authentication.guard_handler')
    ->authenticateUserAndHandleSuccess(
    $user,
    $request,
    $this->get('sng.user.security.login_form_authenticator'),
    'main'
    );

    And the is my error : Détails de l'erreur : Type error: Argument 3 passed to Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken::__construct() must be of the type array, string given, called in /Applications/MAMP/htdocs/shootandgo-dev/vendor/symfony/symfony/src/Symfony/Component/Security/Guard/AbstractGuardAuthenticator.php on line 38

    I don't know why...I'm on v2.8, is it normal to have this exception with this version ?

    thanks

  • 2016-08-23 weaverryan

    Yep, you're absolutely right. So, if the user will click "login" (i.e. not try to access a protected page, and be forced to /login) and then you want it to redirect back to the previous page, you can still do this by setting the redirect URL manually. A good example of that is actually from our Symfony2 version of the tutorial: https://knpuniversity.com/s.... Basically, you manually add the URL you want the user to be sent back to into the session, so that it's there later when your authenticator finishes login. We do this on KnpU on our registration page, to keep you flowing smoothly :).

    Cheers!

  • 2016-08-22 Sean Cooper

    Thanks for the response, I moved past this and have been working on other things, so I haven't had a chance to try this out yet. I will let you know how I get on if I find any thing useful.

    Actually I realise my case is a little different, the user has access some of the content on the page prior to login, so most pages don't redirect to the login form for sign in, which is how I think this example works, so it could be working how it was intended to.

  • 2016-08-08 weaverryan

    Hey Sean!

    Hmm, ok, let's figure this out :). There are 2 places to look:

    A) Make sure you have a return statement on the line where you call the authenticateUserAndHandleSuccess() method. This probably isn't the problem (you mentioned it *redirected* to the registration page, but just in case it was actually just re-rendering) - but had to mention it!

    B) When you call the authenticateUserAndHandleSuccess() method on your authenticator, that is calling the onAuthenticationSuccess() method on your authenticator to determine where to redirect you to. In this tutorial, we never create this method - because we extend AbstractFormLoginAuthenticator, which takes care of this for us (specifically, it checks to see if the user should be redirected back to some secure page, and if there is none, it calls your getDefaultSuccessRedirectUrl() method). However, in Symfony 3.1, there's a slight change where you can implement the onAuthenticationSuccess() method directly yourself. If you do this, then you need to take care of redirecting back to the previous, secure page yourself. But, there's a handy new TargetPathTrait to help you with this (some details here https://knpuniversity.com/s....

    Let me know what you find out, and I'll be happy to offer any other help I can!

    Cheers!

  • 2016-08-07 Sean Cooper

    I have just finished this course, I'm a little stuck on the redirects. The form submits and logs the user in but I then get redirected to the registration page which I have redirecting to the home page of my site.

    I'm unsure where/how I should I handle redirecting to the page prior to the registration form? Have I missed a step somewhere?