0

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?

Roberto Rizzi
  • 1,525
  • 5
  • 26
  • 39
  • Seems this project doesn't use security component :) – Max P. Aug 30 '17 at 13:16
  • I don't see where you are actually logining in the user. Where, for example, is the token storage being updated? – Cerad Aug 30 '17 at 13:16
  • @MaxP. I've just updated my post with my security.yml. – Roberto Rizzi Aug 30 '17 at 13:22
  • @Cerad I'm not sure what the token storage is. I mean, my plan is to generate the token straight after the validation has happened and I haven't done it yet – Roberto Rizzi Aug 30 '17 at 13:25
  • Normally, the builtin form_login functionality would take care of what your checkCredentialsAction method is doing. Just add provider: webservice under form_login and things should work. If you really want to do a manual login I can give you a few bits of code but I don't think you actually do. – Cerad Aug 30 '17 at 13:31
  • @Cerad No, I don't want to do a manual login in fact. I appreciate your comment though. However what do you mean when you say to add webservice unde form_login? I checked the security.yml settings and there is no option for adding a provider – Roberto Rizzi Aug 30 '17 at 13:50
  • It is there someplace. All it does it specify which user provider to use when loading the user. However, you want to load user by username and password which is not supported out of the box. You might consider a guard authenticator: https://symfony.com/doc/current/security/guard_authentication.html – Cerad Aug 30 '17 at 14:03

1 Answers1

2

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.

Cerad
  • 48,157
  • 8
  • 90
  • 92
  • Thank you very much. This works fine. However I'll take a look at Guard, I chose this way because at a glance it wasn't so clear to me and it looked a bit overthought. Just a question about this, why the variable `$token`is it returning an empty string? – Roberto Rizzi Aug 30 '17 at 15:29
  • The token information is stored in the session. It is what allows the app to remember who logged in between requests. I have no idea what you mean about returning an empty string. – Cerad Aug 30 '17 at 15:43
  • yeah fair enough. I got the point. At the end I developed it with guardd. It was easier that I expected. Fortunately this post helped me a lot https://stackoverflow.com/questions/34250444/symfony-2-guard-component-and-a-normal-login-form – Roberto Rizzi Aug 30 '17 at 17:05