The LoginFormAuthenticator
Keep on Learning!
If you liked what you've learned so far, dive in!
Subscribe to get
access to this tutorial plus video, code and script downloads.
76 Comments
Hey Terry,
I think you're right. This place can be error prone in future if the login URL changes. In the next chapter we inject a router into the authenticator, so you can easily reuse it to generate the URL.
Cheers!


I want to use different rules for authentication depending on which subdomain the user is logging in from.
What's the best way to get the subdomain inside of the LoginFormAuthenticator?
Hey Geoff,
As you probably know, $_SERVER does not contain any information about subdomains, but you can get the entire host from it. So, from Symfony's Request object, you can get the host like: $request->server->get('HTTP_HOST'). And if you know the main domain - then with a PHP string function you can easily get the subdomain.
Cheers!


Hello!
First things first:
In script it is: FormLoginAuthenticator and in code's script: LoginFormAuthenticator
The second problem is with Code->generate:
I don't have getDefaultSuccessRedirectUrl(); It's weird. Is it some kind of bug?
Hey, Dominik!
Thanks for let us know, I've fixed wrong authenticator name right now in 1f26635.
Ah, looks like Symfony had some changes due to deprecations in AbstractFormLoginAuthenticator since Symfony 3.1. Did you upgrade to 3.1? What Symfony version do you use?
Cheers!
Ah yes, I think you're right about 3.1 Victor.
So, in 3.1, you can still override getDefaultSuccessRedirectUrl(), but you'll need to add the method to your authenticator yourself (if you go to Code -> Generate in PHPstorm, it won't be added for you anymore).
But more long-term, we removed this method (actually, I did it - it's my fault! https://github.com/symfony/symfony/pull/18135), and instead we want you to add a different method - called onAuthenticationSuccess(). If you're in Symfony 3.1, you should be able to do this:
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Util\TargetPathTrait;
use Symfony\Component\HttpFoundation\RedirectResponse;
class LoginFormAuthentication extends AbstractFormLoginAuthenticator
{
use TargetPathTrait;
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
// if the user hits a secure page and start() was called, this was
// the URL they were on, and probably where you want to redirect to
$targetPath = $this->getTargetPath($request->getSession(), $providerKey);
if (!$targetPath) {
$targetPath = $this->router->generate('homepage');
}
return new RedirectResponse($targetPath);
}
}
Basically, onAuthenticationSuccess() is really the method in charge of things. Previously (and it still works, but is deprecated in 3.1), you could just implement getDefaultSuccessRedirectUrl(), and this method would be taken care of for you. Feel free to do either (use getDefaultSuccessRedirectUrl() or the above code) - both will work, but the above code will be the way forward when Symfony 4 is released in a few years.
Cheers!


Thank you guys for reply!
I'm using symfony 3.1 but for this tutorial i will use the getDefaultSuccessRedirectUrl() method.
As you said it is still few years to symfony 4 ;)
Cheers!
Cheers back! Thanks for bringing this to our attention - I'm sure this conversation will help others with the same question :)
Yo Tony C!
It's not an accident :). Thanks to Symfony's backwards-compatibility promise, the way that's shown in the screencast still works, and will continue to work until Symfony 4.0. I'm actually the one who made this change to Symfony, so I'm well aware of it! We update the screencasts once a new major version of Symfony comes out - it's our way of balancing keeping things up to date, but not constantly re-recording things for tiny changes. It's not a perfect system :). Symfony's backwards compatibility promise helps us (and developers) out a lot with this!
But, in hindsight, I do think a note is in order - if you code with the tutorial perfectly, there's no problems. But if you use the PHPStorm shortcuts for "implement methods", then you won't see all the methods that we get in the recording - and that can be confusing indeed!
Cheers!


Well that's good to know! Thank you!
Backwards compatibility... a blessing and a curse all rolled up in one idea.
Haha, you nailed it - every time Symfony makes a change, I rejoice... and I cringe :D. I just added an issue internally to add a note to this chapter and the next (next chapter is where we actually code up this method).
Cheers!
Hey cybernet2u!
Haha, well, which part exactly? If you're using the LoginFormAuthenticator, the change is here: https://knpuniversity.com/screencast/symfony-security/login-form-authenticator#comment-2760429219. You'll also need to add a new supports() method, which does part of the job of getCredentials() in 4. So, like this:
public function supports(Request $request)
{
// if this returns true, then getCredentials() is called
return $request->getPathInfo() == '/login' && $request->isMethod('POST');
}
public function getCredentials(Request $request)
{
// no need to check that this is the login page anymore - it's done above
$form = $this->formFactory->create(LoginForm::class);
$form->handleRequest($request);
$data = $form->getData();
return $data;
}
Cheers!
For supports()
function, it might be useful to use this line:
public function supports(Request $request)
{
// if this returns true, then getCredentials() is called
return $request->getPathInfo() == $this->router->generate('security_login') && $request->isMethod('POST');
}
It might save us breaking something if we want to change /login
path, and <b>especially</b> if we are dealing with multi-lingual applications :)

Hey Yahya E.
It's not common to change the login's path name, but I totally agree with you, that code is more bullet proof
Cheers!


Hi Ryan, I have implemented this and it works beautifully however I have the impression that "getCredentials" no longer is called on every request... Could this be the case?
I'm want to update a custom field "last_access" on the user entity with a datetimestamp so I can keep track of who was online the last 5 minutes. I figured "getCredentials" would be the perfect place for that but unfortunately it does not get called (only at login).
Hey Dirk!
Ah yes, you're right! Once you update to the new Symfony 4 way (i.e. with the supports()) method, then supports()
IS called on every request. But, now, getCredentials()
is only called when supports() returns true. Actually, this is the whole purpose of supports(): Symfony is asking "Does your authenticator support trying to authenticate this request". If false is returned, getCredentials() is never called.
So, technically speaking, supports() would be the better place to move your last_access logic. But.... putting this logic here is not really the best spot. Simply because your authenticator is all about authentication, and updating this "last_access" is a totally different thing. Instead, I would create an "event susbcriber" that listens on the kernel.request event. This will have a similar effect: the method in your subscriber will be called on every request, and you can do whatever you want. By injecting the Security class (https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/Security.php) into your event subscriber, you can easily get the User object so that you can update their field.
P.S. If you're using the MakerBundle on a Symfony 4 project, you can even generate a subscriber with:
php bin/console make:subscriber
Cheers!


Thanks Ryan! I did create an event subscriber and it works great! However, I was wondering how I can make sure that this is the last subscriber to be launched. The reason is that I'm updating the database with flush() and I do not want to launch other doctrine transactions prematurely... Is lowering the priority of the event an option? If yes, to what level?
Hey Dirk,
Yes, I think priority is an option for you. The priorities of the internal Symfony listeners usually range from -255 to 255 but your own listeners can use any positive or negative integer, so I suppose you can use a number which is lower than -255 to be sure your subscriber will be called *after* any Symfony one. And of course, make sure all your other listeners do not have a lower priority than the priority for that last subscriber. But it also sounds like a good spot to add some integration tests, at least to make sure everything works and you won't get any exceptions.
Cheers!


Great! Thanks Victor, so far it works as expected with a priority of -300.
Best regards,
Dirk


that tutorial i already followed, and even if the form was submitted, symfony never saw me as authenticated :( ( i did use the supports method )... now it's fixed, with other tutorial
Yo cybernet2u!
Awesome :). Well, I'm happy it's fixed now :).


weaverryan In the original implementation that is now deprecated you had the following lines that you no longer have... should we still have them, or do like the above and leave them out?
$targetPath = null;
// This comment wasn't in there, but the contents of this if is your $targetPath = line.
if ($request->getSession() instanceof SessionInterface) {}
Yo JSThePatriot!
Ah, interesting! That was a line that I didn't originally add to AbstractFormLoginAuthenticator
, but I see it! In the framework (unless you're doing something crazy), you shouldn't need this. In theory, you could configure things to not have a Session object at all - so getSession()
would return null (I'm not sure why they did it as an instanceof check, but they're basically checking to see if the Session exists). Feel free to keep it in, but you won't need it.
Good question man! Cheers!


Hey Ryan,
It all works until... onAuthenticationSuccess has to be called. If it isn't there, explosion. If it is there, no authentication required and I get taken to the page I requested, behind the firewall...
Hey odds!
Hmm. So if you're on Symfony 3.1, then you should have onAuthenticationSuccess
, but having it is technically optional until Symfony 4 (if you don't have it, you'll just receive a warning). But in either case, that method should have nothing to do with whether or not authentication is required for some endpoint - that's very strange. Let's debug!
A) If you don't have onAuthenticationSuccess, what is the error?
B) When you are taken to the page where no authentication is required, what do you see in the web debug toolbar for security? Does it say "anon"? And what code should be forcing authentication on that page? Do you have an access_control
or a denyAccessUnlessGranted
in a controller?
Let me know - we'll figure it out!
Cheers!


Hey Ryan,
I was a little bit to early with my conclusion. It works, but the toolbar does not show my username. That's why I thought something went horribly wrong...
Now trying to figure out why the toolbar shows n/a instead of my username...
Hey odds!
Ah, fascinating! The toolbar shows n/a? Hmm, what does your firewall look like? Usually, that toolbar only either shows (A) your username or (B) anon, if you're not logged in. n/a almost makes me think that the URL you're visiting isn't covered by your firewall. But that's just a first guess!
Cheers!


Hey Ryan!
I coded everything by the new updated way. So I'm on the login page and after I hit submit, it does nothing and always redirects back to the login page. It doesn't matter if using the correct password or a wrong one.
In the profiler the POST values are there, but in the form section it shows null.
UPDATE: After some extensive google search I got it working.
public function getCredentials(Request $request)
{
$isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod('POST');
if ($isLoginSubmit) {
$req = $request->request->get('login_form');
return [
'_username' => $req['_username'],
'_password' => $req['_password'],
];
}
$form = $this->formFactory->create(LoginForm::class);
$form->handleRequest($request);
$data = $form->getData();
dump($data);
return $data;
}
Hey Attila László!
Ah, awesome! Usually, when you submit and see nothing it's either because your authenticator is not being called (i.e. it's not configured correctly in security.yml) or you have a bug in your getCredentials() method, so that it is not returning the credentials when it should.
In this case, it looks like you basically added some code to skip the form framework and instead grab the POST information directly from the request. That's a great way to do it, and actually, I wish I had done it this way - it's a bit simpler with no downside. You should be able to simplify your code:
public function getCredentials(Request $request)
{
$isLoginSubmit = $request->getPathInfo() == '/login' && $request->isMethod('POST');
if ($isLoginSubmit) {
$req = $request->request->get('login_form');
return [
'_username' => $req['_username'],
'_password' => $req['_password'],
];
}
// just return null: do no processing on this request
return;
}
I'm not sure what the problem was in your case... the form should have been processing the data just fine. But, I like your approach better anyways :).
Cheers!


Thx for the reply!
These are the files in question:
- https://pastebin.com/BTaQAEtX
- https://pastebin.com/CG1ET7CH
- https://pastebin.com/FWxzcz1m
- https://pastebin.com/TKfFzQhW
Hope we can figure it out, because I really want know these things.
Hey!
Can you post your login.html.twig and LoginFrom.php file also?
Cheers!


Hey Attila László!
It's still a mystery to me! You're rendering your form correctly and handling the request in the authenticator just fine. From everything I can see, it *should* be properly processing the data through the form! So, I'm sorry I can't give you an answer! As I mentioned earlier, the way that does *not* use the form in the getCredentials() method is a little simpler/nicer anyways in my opinion (and I'll use that way personally on the future). So, stick with that :).
Cheers!


Sjeeez, it was waaay more basic... forgot the 'return' in getUsername()...
Thanks anyway!


And is this a public or protected function? Since getDefaultSuccessRedirectUrl was protected...


Well, that's one question answered. This method should definitly be public (Symfony says so ;-) ), but than the shit hits the fan. The method getTargetPath is private (big explosion) and getDefaultSuccessRedirectUrl is not found... So, the stuff from the video is out of date and the stuff in the comment is incomplete. Bummer...
Hey Hermen!
The getTargetPath
function comes from the TargetPathTrait
. Make sure you're "using" this in your class. And you're right that getTargetPath
is private! But that's ok - it's legal to use a private method from a trait (effectively, when you "use" a trait, its methods are copied into your class - so you CAN access private methods).
Let me know if that helps!
I just looked at the TargetPathTrait
and it only has 3 functions, Save, Get and Remove. The getDefaultSucessRedirectUrl
has been removed - v3.2.3.
Am I looking at the wrong spot? or should I just do the redirect myself (Figured what the hell, just create it until I get a response :P )?
Peter Ah, you're right! I mis-spoke in my previous message! Basically, the solution in the video works, but we made some changes in Symfony, so the new "non-deprecated" version is available above in my comment: https://knpuniversity.com/screencast/symfony-security/login-form-authenticator#comment-2760429219
Basically, you should now implement onAuthenticationSuccess
yourself, and inside, you'll make use of getTargetPath()
. If you do this, then you don't need the getDefaultSuccessRedirectUrl
at all :). But, I can see now that my code-block above is mis-leading on this! So, Peter, you did the right thing by adding getDefaultScucesRedirectUrl
yourself, but it's actually not needed. Check (in about 2 minutes) my updated code above the comment I linked to :).
Cheers!
And actually, I had some misleading code in my comment above! You should not need to call getDefaultSuccessRedirectUrl
at all anymore in the new way - my misleading code is what confused you on this! I've just updated the original code in my comment above with the not-misleading, proper way.
Cheers!
there Thank you for keeping the tutorial updated with new Symfony releases.

Hey there
is this due to the version of symfony? It may... How are you injecting the "formFactory" argument?


Thank you Diego, Yeah absolutely I have a problem with injection 'autowire'.
thank you very much.

In that case you have to explicitly enable it by adding autowire: true
in your service definition.


I must be missing something. I've been following the course with 3.3.12 and up until the autowiring, which I skipped (since 3.3.12 does that for me). Now I'm getting the error "Class Symfony\Component\Security\Guard\Authenticator\AbstractFormLoginAuthenticator not found" for some reason. If I add 'Security' to my exclude pattern, the service cannot be found ("exclude: '../../src/AppBundle/{Entity,Repository,Tests,Security}'"). I'm currently at a loss as how this is occurring...


Alright. I am that guy that honestly posts after 1,5 hour of searching and after hitting enter finds out that he corrupted the AbstractFormLoginAuthenticator class @trigger_error portion by reading up and removing the semicolon.
Eventually I also found out that declaring _defaults in the services.yml doesn't work globally. I'm also using AppBundle specific Resources which also include a set of services (in the .yml file). Upon copying those defaults introduced in 3.3 it all worked like a charm.
At about the 2 minute mark, you hard code "/login". It would be better to somehow call the path "security_login" created in the routing of the controller, correct? In case "/login" later changes to something else.