I have a simple login form:
public function loginAction(Request $request)
{
$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();
return $this->render(
'users/login.html.twig',
[
'page' => 'login',
'last_username' => $lastUsername,
'error' => $error,
]
);
}
security.yml:
secured_area:
form_login:
provider: user_db_provider
login_path: /login
check_path: /login
csrf_token_generator: security.csrf.token_manager
And I'm trying to set the user's last login time.
Do I really have to create a new listener and write an entire class, like this answer, just for this single instruction: $user->setLastLogin(new \DateTime());
? Or is there something simpler, like putting it in the controller?
Yes, you must create a login success listener, as described in this answer, and use the onAuthenticationSuccess
method to store your user information.
The code you posted is for generating the login page itself. Any login action is handled by the firewall, which calls either a success or failure listener. You can execute your code (for example setting the last successful login timestsamp) in your custom listener.
The reason for that is that it is better to create classes that perform a single task, rather than putting a lot of code together in one class. This maintains your modularity, extensibility and maintanability of your code.
Well, in Symfony 4 I'm using another approach, since I don't have FOSUserBundle in my project and I don't like use onAuthenticationSuccess cause is called every time
(..)beware - this event (onAuthenticationSuccess) will fire, for example, on every request if you have session-based authentication (..)
the user is checked for authentication ( see this security authentication section).
I know that is possible to use security.interactive_login
EventListener (better way), but for other reasons I prefer to do with the steps above.
So I created a simple route on SecurityController to check and update User->lastLogin attribute.
// /config/packages/security.yaml
security:
firewalls:
main:
pattern: ^/
user_checker: App\Security\UserChecker
form_login:
provider: my_db_provider
login_path: login
check_path: login
always_use_default_target_path: true
default_target_path: check_last_login
csrf_token_generator: security.csrf.token_manager
Here, always_use_default_target_path: true
and default_target_path: check_last_login
are the keys
always_use_default_target_path: true forces the target and
check_last_login is the route name to redirect when actually user logs in
// /src/Controller/SecurityController.php
/**
* @param Request $request
* @Route("/check-last-login", name="check_last_login")
* @param Request $request
* @return Response
*
*/
public function checkLastLogin(Request $request)
{
/* @var $user User */
if (!$user = $this->getUser()) {
// redirect to somewhere to show some message
throw new someAuthenticationException();
}
if ($user->getLastLogin() == null) {
// Do something when is the first login
return $this->redirectToRoute('complete-register');
}
$user->setLastLogin(new \DateTime());
$oManager = $this->getDoctrine()->getManager();
$oManager->persist($user);
$oManager->flush();
//
return $this->forward('App\Controller\DefaultController::index', array($request));
}
The forward() can be replaced to
// almost the time $providerKey is 'main' - see your firewalls entries
$providerKey = 'main';
$targetPath = $request->getSession()->get('_security.'.$providerKey.'.target_path');
$this->redirect($targetPath);
But keep in mind that can be the logout route, so you will need to avoid a successfull login followed by a logout
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With