Why is Symfony Guard triggering a "security.interactive_login" event on every request?
I have a Symfony 3.2 application that provides REST API and uses Json Web Tokens (JWT) for authentication. I recently switched to using the Symfony Guard component. Mine now security.yml
contains a firewall config section like this (I'm using Lexik JWT 2.4.0 package, but it doesn't matter):
firewalls:
# ...
api:
pattern: ^/api
stateless: true
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
Since I made this switch, I noticed that every request is handled as if the user had just logged in, i.e. an event security.interactive_login
. The docs ( http://symfony.com/doc/current/components/security/authentication.html#authentication-events ) says:
The security.interactive_login event is triggered after active use by a user logged into your site. It is important to distinguish this action from non-interactive authentication methods such as remember me cookie-based authentication, session-based authentication, authentication using the main HTTP header or HTTP header. You can listen for the security.interactive_login event, for example, to give your user a welcome flash message every time they log in.
So I definitely don't expect this event for every request - I would rather get an event security.authentication.success
for every request as stated in the docs.
However, the Symfony class GuardAuthenticatorHandler
dispatches an event security.interactive_login
to its method authenticateWithToken
, and this method is called GuardAuthenticationListener
for every request. Is this a bug in Symfony, misunderstanding on my side or misconfiguration?
(This is not a philosophical question - in my case it leads to a specific problem that the user's last login time is updated on every request, which doesn't make sense.)
source to share
I ran into your problem because I have exactly the same problem. My workaround is to add the attribute to the request object, right before returning true in the backing guard method.
Example:
public function supports(Request $request)
{
...
$request->attributes->set('is_interactive_login', true);
return true;
}
With this information you can check if it was an interactive input to an event listener
Example:
public function onLoginSuccess(InteractiveLoginEvent $event)
{
$request = $event->getRequest();
if ($request->attributes->get('is_interactive_login', false)) {
// do whatever you need todo on interactive login
}
}
source to share
It is better to subscribe to the event Events::JWT_CREATED
as it is Events::JWT_CREATED
after being authenticated with credentials.
Example:
<?php
namespace App\Event\Subscriber;
use App\Entity\User\User;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTCreatedEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Events;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class AuthenticationSuccessSubscriber implements EventSubscriberInterface
{
/**
* @var EntityManager
*/
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
return [
Events::JWT_CREATED => 'onInteractiveLogin',
];
}
/**
* @param JWTCreatedEvent $event
*
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function onInteractiveLogin(JWTCreatedEvent $event)
{
/** @var User $user */
$user = $event->getUser();
$user->setLastLoginAt(new \DateTime());
$user->resetFailedLogins();
$this->em->flush($user);
}
}
source to share
I faced the same problem. In my case, I am using Guard authentication for API requests. So I definitely don't like updating user last_login after any API request.
The INTERACTIVE_LOGIN event is dispatched from here .
So my dirty hack is to add this definition to services
the application config section :
security.authentication.guard_handler:
class: Symfony\Component\Security\Guard\GuardAuthenticatorHandler
arguments:
$eventDispatcher: ~
Warning
The obvious downside to this approach is that you have broken the change handler for all of your application defenders.
source to share