I'm building my first API login with Symfony3 but I tripped up on the login listener. I want to have an event fired as soon as the user has successfully logged in for the various, usual purposes such as write a log, generate a token etc. Because things are happening over API, the login system is a bit different from the classic login form described in Symfony guides. In this light, I'm sure there's something I missed.
Listener initialisation:
// config/services.yml
//...
login_listener:
class: 'User\LoginBundle\Listener\LoginListener'
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login', method: onSecurityInteractiveLogin }
My Listener:
// User/LoginBundle/Listener/LoginListener.php
namespace User\LoginBundle\Listener;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
class LoginListener
{
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
echo 'Hello, I am the login listener!!';
}
}
my controller class
// User/LoginBundle/Controller/LoginController.php
//...
public function checkCredentialsAction(Request $request)
{
$recursiveValidator = $this->get('validator');
$user = new User;
$user->setUsername($request->request->get('username'));
$user->setPassword($request->request->get('password'));
$errors = $recursiveValidator->validate($user);
if (count($errors) > 0) {
$errorsString = (string) $errors;
return new JsonResponse($errorsString);
}
$loginService = $this->get('webserviceUserProvider.service');
$user = $loginService->loadUserByUsernameAndPassword(
$request->get('username'),
$request->get('password')
);
if ($user instanceof WebserviceUser) {
return new JsonResponse('all right');
}
return new JsonResponse('Username / password is not valid', 403);
}
My security component
security:
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
in_memory:
memory: ~
api_key_user_provider:
id: AppBundle\Security\ApiKeyUserProvider
# property: apiKey
user_db_provider:
entity:
class: UserUserBundle:User
# property: username
webservice:
id: User\UserBundle\Security\User\WebserviceUserProvider
encoders:
User\UserBundle\Entity\User:
algorithm: bcrypt
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
user_logged:
pattern: ^/logged
stateless: true
simple_preauth:
authenticator: AppBundle\Security\ApiKeyAuthenticator
provider: api_key_user_provider
main:
anonymous: ~
form_login:
check_path: login/check
access_control:
- { path: ^/login/check, roles: IS_AUTHENTICATED_ANONYMOUSLY }
As you can see the validation is carried out against the entity and when user / password is valid, the json returned is return new JsonResponse('all right, you are logged in');
. The validation is done in a Custom User Provider class instantiated as service (method loadUserByUsernameAndPassword
, which it is pretty similar to this),
Why when user and password are valid and the login occurs, the listener isn't taken into account as a valid event to make fire the interactive_login
event?
If for some reason you really have the need to manually login an user then add this method to your controller and call at the appropriate time:
private function loginUser(Request $request, UserInterface $user)
{
$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->get("security.token_storage")->setToken($token);
$event = new InteractiveLoginEvent($request, $token);
$this->get("event_dispatcher")->dispatch(SecurityEvents::INTERACTIVE_LOGIN, $event);
}
if ($user instanceof WebserviceUser) {
$this->loginUser($request,$user);
return new JsonResponse('all right');
}
However, in most cases the existing authentication system (or a custom guard authenticator, https://symfony.com/doc/current/security/guard_authentication.html) will do this for you.