Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manually authenticate user after Registration with the new Symfony 5 Authenticator?

Symfony 5 has changed its guard authentication method to a new Passport based one, using the new security config: enable_authenticator_manager: true;

I would like to know how to authenticate a user in the Registration form method in my controller, after the user is persisted by the ORM (Doctrine);

I have succeeded in authenticating the user using the login form, but I still do not know how to manually do this.

like image 349
Rodolfo Rangel Avatar asked Apr 02 '21 20:04

Rodolfo Rangel


People also ask

What is manual authentication?

Manual authentication prompts users for a user name and password the first time they access the Internet through a browser. Websense software confirms the password with a supported directory service, and then retrieves policy information for that user.

Is Symfony secure?

Symfony provides many tools to secure your application. Some HTTP-related security tools, like secure session cookies and CSRF protection are provided by default. The SecurityBundle, which you will learn about in this guide, provides all authentication and authorization features needed to secure your application.


4 Answers

As per Cerad's comment, here is the full answer.

Below is only the part of the code related to the question & answer. These are not the full files.

Also, this is only for Symfony ^5.2 that is not using guard to authenticate the user.

/* config/packages/security.yaml */

security:
    enable_authenticator_manager: true
    firewalls:
        main:
            custom_authenticators:
                - App\Security\SecurityAuthenticator
/* src/Security/SecurityAuthenticator.php */

use Symfony\Component\Security\Http\Authenticator\AbstractLoginFormAuthenticator;

/* automatically generated with the make:auth command,
     the important part is to undestand that this is not a Guard implement 
     for the Authenticator class */
class SecurityAuthenticator extends AbstractLoginFormAuthenticator
{
  
}
/* src/Controller/RegistrationController.php */

use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\SecurityAuthenticator;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;

class RegistrationController extends AbstractController
{

    /**
     * @Route("/register", name="app_register")
     */
    public function register(
        Request $request, 
        UserPasswordEncoderInterface $passwordEncoder, 
        UserAuthenticatorInterface $authenticator, 
        SecurityAuthenticator $formAuthenticator): Response
    {
      /* Automatically generated by make:registration-form, but some changes are
         needed, like the auto-wiring of the UserAuthenticatorInterface and 
         SecurityAuthenticator */
        $user = new User();
        $form = $this->createForm(RegistrationFormType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // encode the plain password
            $user->setPassword($passwordEncoder->encodePassword($user, $form->get('password')->getData()));

            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->persist($user);
            $entityManager->flush();

            // substitute the previous line (redirect response) with this one.
            return $authenticator->authenticateUser(
                $user, 
                $formAuthenticator, 
                $request); 
        }

        return $this->render('registration/register.html.twig', [
            'registrationForm' => $form->createView(),
        ]);
    }
}
like image 168
Rodolfo Rangel Avatar answered Oct 21 '22 11:10

Rodolfo Rangel


Symfony 5.3 it's work for me

public function register(Request $request, Security $security, UserPasswordEncoderInterface $passwordEncoder, EventDispatcherInterface $dispatcher) {


......

$token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
$this->get("security.token_storage")->setToken($token);

$event = new SecurityEvents($request);
$dispatcher->dispatch($event, SecurityEvents::INTERACTIVE_LOGIN);
return $this->redirectToRoute('home');

like image 44
Mucahid CAKMAK Avatar answered Oct 21 '22 11:10

Mucahid CAKMAK


For Symfony 6 find working solution, based on @Cerad's comment about UserAuthenticatorInterface::authenticateUser().

I declared my RegisterController in services.yaml with important argument (it is the reason):

App\Controller\RegisterController:
    arguments:
        $authenticator: '@security.authenticator.form_login.main'

So my RegisterController now looks like:

class RegisterController extends AbstractController
{
    public function __construct(
        private FormLoginAuthenticator $authenticator
    ) {
    }

    #[Route(path: '/register', name: 'register')]
    public function register(
        Request $request,
        UserAuthenticatorInterface $authenticatorManager,
    ): RedirectResponse|Response {
        // some create logic
        ...

        // auth, not sure if RememberMeBadge works, keep testing
        $authenticatorManager->authenticateUser($user, $this->authenticator, $request, [new RememberMeBadge()]);
    }
}
like image 5
Max Gorovenko Avatar answered Oct 21 '22 11:10

Max Gorovenko


Here's my go at it, allowing you to authenticate a user, and also attach attributes to the generated token:

// src/Service/UserService.php
<?php

namespace App\Service;

use App\Entity\User;
use App\Security\LoginFormAuthenticator;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticatorManager;
use Symfony\Component\Security\Http\Authenticator\AuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\InteractiveAuthenticatorInterface;
use Symfony\Component\Security\Http\Authenticator\Passport\Badge\UserBadge;
use Symfony\Component\Security\Http\Authenticator\Passport\SelfValidatingPassport;
use Symfony\Component\Security\Http\Event\AuthenticationTokenCreatedEvent;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

class UserService
{

    private AuthenticatorInterface $authenticator;
    private TokenStorageInterface $tokenStorage;
    private EventDispatcherInterface $eventDispatcher;

    // This (second parameter) is where you specify your own authenticator,
    // if you have defined one; or use the built-in you're using
    public function __construct(
        LoginFormAuthenticator $authenticator,
        TokenStorageInterface $tokenStorage,
        EventDispatcherInterface $eventDispatcher
    ) {
        $this->authenticator = $authenticator;
        $this->tokenStorage = $tokenStorage;
        $this->eventDispatcher = $eventDispatcher;
    }

    /**
     * @param User $user
     * @param Request $request
     * @param ?array $attributes
     * @return ?Response
     *
     */
    public function authenticate(User $user, Request $request, array $attributes = []): ?Response
    {
        $firewallName = 'main';

        /** @see AuthenticatorManager::authenticateUser() */

        $passport = new SelfValidatingPassport(
            new UserBadge($user->getUserIdentifier(), function () use ($user) {
                return $user;
            })
        );

        $token = $this->authenticator->createAuthenticatedToken($passport, $firewallName);
        /** @var TokenInterface $token */
        $token = $this->eventDispatcher->dispatch(
            new AuthenticationTokenCreatedEvent($token, $passport)
        )->getAuthenticatedToken();

        $token->setAttributes($attributes);

        /** @see AuthenticatorManager::handleAuthenticationSuccess() */

        $this->tokenStorage->setToken($token);
        $response = $this->authenticator->onAuthenticationSuccess($request, $token, $firewallName);

        if ($this->authenticator instanceof InteractiveAuthenticatorInterface && $this->authenticator->isInteractive()) {
            $loginEvent = new InteractiveLoginEvent($request, $token);
            $this->eventDispatcher->dispatch($loginEvent, SecurityEvents::INTERACTIVE_LOGIN);
        }

        $this->eventDispatcher->dispatch(
            $loginSuccessEvent = new LoginSuccessEvent(
                $this->authenticator,
                $passport,
                $token,
                $request,
                $response,
                $firewallName
            )
        );

        return $loginSuccessEvent->getResponse();
    }

}

Largely inspired from AuthenticatorManager::authenticateUser() and AuthenticatorManager::handleAuthenticationSuccess().

like image 1
Quentin Avatar answered Oct 21 '22 11:10

Quentin