6

I have a Symfony 3.2 application which exposes a REST API and uses Json Web Tokens (JWT) for authentication. I recently switched to using Symfony's Guard component. Now my security.yml contains a firewall config section as follows (I'm using the Lexik JWT bundle 2.4.0, but this shouldn't matter):

firewalls:
    # ...
    api:
        pattern:   ^/api
        stateless: true
        guard:
            authenticators:
               - lexik_jwt_authentication.jwt_token_authenticator

Since I did this switch, I notice that every request is handled as if the user just logged in, i.e. a security.interactive_login event is fired. In the docs (http://symfony.com/doc/current/components/security/authentication.html#authentication-events) it states:

The security.interactive_login event is triggered after a user has actively logged into your website. It is important to distinguish this action from non-interactive authentication methods, such as: authentication based on a "remember me" cookie, authentication based on your session, authentication using a HTTP basic or HTTP digest header. You could listen on the security.interactive_login event, for example, in order 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'd rather expect to get the security.authentication.success event on every request, as pointed out in the docs.

However, Symfony's GuardAuthenticatorHandler class dispatches the security.interactive_login event in its authenticateWithToken method, and this method is called by the GuardAuthenticationListener on every request. Is that a bug in Symfony, a misunderstanding on my side, or due to incorrect configuration?

(This is not a philosophical question - in my case it leads to the concrete problem that the last login time of the user is updated on every request, which does not make sense.)

dwytrykus
  • 256
  • 2
  • 7

4 Answers4

1

You should change this

        stateless: false
ste
  • 1,479
  • 10
  • 19
  • Can you elaborate why? To me it sounds like JWT authenticated requests are used to have a stateless conversation. – LBA May 24 '17 at 20:17
  • 1
    This is explained here http://symfony.com/doc/current/security/api_key_authentication.html#storing-authentication-in-the-session if you set stateless true then you don't store the authentication in the session and you are expected to authenticate every time. – ste May 25 '17 at 19:48
1

I've come across your issue, because I've exactly the same problem. My workaround is to add a attribute in the request object, right before return true in the supports method of the guard.

Example:

public function supports(Request $request)
{
    ...

    $request->attributes->set('is_interactive_login', true);

    return true;
}

With this information you can check if it was a interactive login in the 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
    }
}
ddegasperi
  • 662
  • 9
  • 12
1

Better to subscribe to Events::JWT_CREATED event, as it's fired after authentication with credentials has been passed.

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);
    }
}
0

I've faced with same trouble. In my case I use Guard authentication for API requests. So I definitely don't like updating user's last_login after any API request.

INTERACTIVE_LOGIN event is dispatched from here.

So my dirty hack is to add this definition to the services section of app's config:

security.authentication.guard_handler:
        class: Symfony\Component\Security\Guard\GuardAuthenticatorHandler
        arguments:
            $eventDispatcher: ~

Warning

Obvious drawback of this approach is that you broke change handler for all your app's guards.

Storm
  • 360
  • 4
  • 16