Reputation: 757
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.
Upvotes: 19
Views: 15888
Reputation: 333
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()
.
Upvotes: 2
Reputation: 181
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()]);
}
}
Upvotes: 18
Reputation: 31
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');
Upvotes: 3
Reputation: 757
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(),
]);
}
}
Upvotes: 17
Reputation: 3334
This might work depending on your setup. Note that main
in the authenticateUserAndHandleSuccess()
method is the name of my firewall in config/packages/security.yaml
and LoginFormAuthenticator
is the authenticator I created using bin/console make:auth
.
/**
* @Route("/register", name="app_register")
* @param Request $request
* @param EntityManagerInterface $entityManager
* @param GuardAuthenticatorHandler $handler
* @param LoginFormAuthenticator $authenticator
* @param UserPasswordEncoderInterface $encoder
*
* @return Response
*/
public function register(
Request $request, EntityManagerInterface $entityManager, GuardAuthenticatorHandler $handler,
LoginFormAuthenticator $authenticator, UserPasswordEncoderInterface $encoder
): Response {
$user = new User();
$form = $this->createForm(RegisterType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$plainPassword = $form->get('plainPassword')->getData();
$user->setPassword($encoder->encodePassword($user, $plainPassword));
$entityManager->persist($user);
$entityManager->flush();
$handler->authenticateUserAndHandleSuccess($user, $request, $authenticator, 'main');
}
return $this->render('security/register.html.twig', [
'form' => $form->createView()
]);
}
Upvotes: 0