im trying to make a login method with symfony 4 , i have create this method public function
getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
if (!$user) {
return new RedirectResponse($this->urlGenerator->generate('app_login'));
}
return $user;
}
when i put a false user it display to me this error:
The "App\Security\UserAuthenticator::getUser()" method must return a UserInterface. You returned "Symfony\Component\HttpFoundation\RedirectResponse".
I'm guessing you are using a simple FormAuthenticator extending AbstractFormLoginAuthenticator (which is deprecated for symfony 5.3+ with the new authenticator system).
getUser() should only return an user or throw an exception so you should remove your redirect with:
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
if (!$user) {
throw new UserNotFoundException('User not found.');
}
return $user;
}
(You may want to use a less descriptive exception for security reason).
If I remember how it looked like in 4.4, your login method in your controller should look like:
use Symfony\Component\HttpFoundation\Request\AuthenticationUtils;
/**
* #Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig',
[
'last_username' => $lastUsername,
'error' => $error,
]
);
}
I have a problem. I created verificator via Mailtrap.io and standart login form via Symfony console. And then I want to check user before login via email if his email has been verified. So, how can I redirect from LoginFromAuthenticator and add flash to notificated them?
class LoginFromAuthenticator extends AbstractLoginFormAuthenticator
{
use TargetPathTrait;
public const LOGIN_ROUTE = 'main_login';
private UrlGeneratorInterface $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function authenticate(Request $request): Passport
{
$email = $request->request->get('email', '');
$request->getSession()->set(Security::LAST_USERNAME, $email);
return new Passport(
new UserBadge($email),
new PasswordCredentials($request->request->get('password', '')),
[
new CsrfTokenBadge('authenticate', $request->request->get('_csrf_token')),
new RememberMeBadge()
]
);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
{
if ($targetPath = $this->getTargetPath($request->getSession(), $firewallName)) {
return new RedirectResponse($targetPath);
}
return new RedirectResponse($this->urlGenerator->generate('main_homepage'));
}
protected function getLoginUrl(Request $request): string
{
return $this->urlGenerator->generate(self::LOGIN_ROUTE);
}
}
class SecurityController extends AbstractController
{
#[Route(path: '/login', name: 'main_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
// if ($this->getUser()) {
// return $this->redirectToRoute('target_path');
// }
// get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError();
// last username entered by the user
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig', ['last_username' => $lastUsername, 'error' => $error]);
}
#[Route(path: '/logout', name: 'main_logout')]
public function logout(): void
{
throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
}
I'm writing a method in my Symfony 3 application for bulk user creation. The flux is uploading a csv file with all the necessary data.
I created a Service, into I write all the logic of this operation. This is my Service:
class BulkRegistration
{
private $em;
private $validator;
private $session;
public function __construct(EntityManagerInterface $em, ValidatorInterface $validator, SessionInterface $session)
{
$this->em = $em;
$this->validator = $validator;
$this->session = $session;
}
public function run(BulkRegistrationData $bulkRegistrationData){
//todo rimuovere dipendenza nascosta
$serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
$datas = $serializer->decode(file_get_contents($bulkRegistrationData->csv), 'csv');
$this->em->getConnection()->beginTransaction();
try{
foreach($datas as $data)
{
$userData = UserData::create($data);
$this->validate($userData, 'newUser');
$userCreate = User::create($userData->user);
$this->em->persist($userCreate);
$this->em->flush();
}
$this->em->getConnection()->commit();
} catch (\Exception $e) {
$this->em->getConnection()->rollback();
$this->em->close();
$this->session->getFlashBag()->add('error', $e->getMessage());
return false;
}
return true;
}
private function validate ($entity, $validationGroup = null){
if($validationGroup){
$errors = $this->validator->validate($entity, null, [$validationGroup]);
}else{
$errors = $this->validator->validate($entity);
}
if (count($errors) > 0) {
$errorMessage = '';
foreach($errors as $error)
{
$errorMessage .= $error->getMessage();
}
throw new \Exception($errorMessage);
}
return;
}
}
Also I wrote this EmailSubscriber, for sending an activation email each time the entity User is persisted:
class EmailSubscriber implements EventSubscriber
{
private $activationEmail;
public function __construct(SendActivationEmail $activationEmail)
{
$this->activationEmail = $activationEmail;
}
public function getSubscribedEvents()
{
return array(
Events::postPersist,
);
}
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
if ($entity instanceof User)
{
$this->activationEmail->send($entity);
}
}
}
And this is question:
The EventSubscriber catch the persisted event before the transaction commit.
I want or persist all the row in my db, or response with violation and ask to User to modify his csv file.
Because this, one of the useCase can be some activation email sended but no persisting the User in DB, for example for some validate violation of one of the csv row.
I hope I was crearl, the case is a bit intricate.
I think you need to adjust the foreach to only flush if there were no errors:
foreach($datas as $data) {
$userData = UserData::create($data);
try {
$this->validate($userData, 'newUser');
} catch (\Exception $e) {
$this->em->getConnection()->rollback();
$this->em->close();
$this->session->getFlashBag()->add('error', $validation);
return false;
}
$userCreate = User::create($userData->user);
$this->em->persist($userCreate);
}
$this->em->flush();
$this->em->getConnection()->commit();
return true;
i have problem with HWIOAuthBundle and google authentication, i can't complete work on OAuthProvider. After flush data, i want return entity object, that i saw example somewhere in stackoverflow.
But when i return $obj;
I catch error :
Catchable Fatal Error: Argument 2 passed to HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken::__construct() must be of the type array, integer given, called in /var/www/integra/vendor/hwi/oauth-bundle/Security/Core/Authentication/Provider/OAuthProvider.php on line 109 and defined
Construct this class :
public function __construct($accessToken, array $roles = array())
{
parent::__construct($roles);
$this->setRawToken($accessToken);
parent::setAuthenticated(count($roles) > 0);
}
Then i return:
return new JsonResponse(['accessToken' => $user->getToken(), 'Roles' => $user->getRoles()]); // I catch error what it loadUserByOAuthUserResponse() must return a UserInterface
class OAuthProvider extends OAuthUserProvider
{
protected $container, $em;
public function __construct(\Doctrine\ORM\EntityManager $em, $container)
{
$this->container = $container;
$this->em = $em;
}
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$email = $response->getEmail();
if ($email === null) {
throw new NotFoundHttpException("User email adress not found", 404);
}
$name = $response->getFirstName();
$surname = $response->getLastName();
$photo = $response->getProfilePicture();
$repository = $this->em->getRepository('IdamasBillingBundle:User');
$user = $repository->searchByNameSurnameEmail($email);
if($user){
$login = new User();
$login->setEmail($email);
$session = $this->container->get('session');
$session->set('login', $login);
return $user;
} else {
$user = new User();
$user->setEmail($email);
$user->setName($name);
$user->setSurname($surname);
$user->setPosition('Just user');
$user->setRoles(1);
$user->setPhoto($photo);
$this->em->persist($user);
$this->em->flush();
$session = $this->container->get('session');
$session->set('login', $user);
// return $user;f
return new JsonResponse(['accessToken' => $user->getToken(), 'Roles' => $user->getRoles()]);
}
//return new RedirectResponse("/billing");
}
}
How i can to do it, that redirect to complete login page?
User object should have roles property, and it must be an array:
class User {
protected $roles = [];
public function getRoles() {
return $this->roles;
}
public function addRole($role) {
$this->roles[] = $role;
}
}
I'm working on a creating a custom authentication provider. I've written my own Authentication Provider, Listener, Token and everything. It's based off a form login, and I've stepped through the code and everything seems to be configured properly. Everything is called in the right order, and my authentication provider is invoked perfectly. The authentication provider successfully authenticates the user, and returns the authenticated token. I extend AbstractAuthenticationListener which, in the handle method, will set the security context.
The user seems to be logged in, but in the debug toolbar, the token is not set and I see "You are not authenticated" and "No token".
Is there any configuration settings that I'm missing? Why would the user would be logging in, authentication provider returning successfully, with an authenticated token, being set in the security context but still be not authenticated? Any tips on how to debug this?
(I will post code as needed.)
EDIT: Token Definition:
This is very simple, just extending from AbstractToken:
class UserToken extends AbstractToken
{
private $username;
private $password;
private $domain;
private $providerKey;
public function __construct($username, $password, $domain, $provider_key, array $roles = array('ROLE_USER'))
{
parent::__construct($roles);
$this->username = $username;
$this->password = $password;
$this->domain = $domain;
$this->providerKey = $provider_key;
}
public function getCredentials()
{
return '';
}
function getUsername() {
return $this->username;
}
function getDomain() {
return $this->domain;
}
function getPassword() {
return $this->password;
}
function getProviderKey(){
return $this->providerKey;
}
}
Authentication Listener:
class Listener extends AbstractAuthenticationListener
{
protected $authenticationManager;
public function __construct(
SecurityContextInterface $securityContext,
AuthenticationManagerInterface $authenticationManager,
SessionAuthenticationStrategyInterface $sessionStrategy,
HttpUtils $httpUtils,
$providerKey,
AuthenticationSuccessHandlerInterface $successHandler,
AuthenticationFailureHandlerInterface $failureHandler,
array $options = array(),
LoggerInterface $logger = null,
EventDispatcherInterface $dispatcher = null
//CsrfProviderInterface $csrfProvider = null
) {
parent::__construct(
$securityContext,
$authenticationManager,
$sessionStrategy,
$httpUtils,
$providerKey,
$successHandler,
$failureHandler,
array_merge(
array(
'username_parameter' => '_username',
'password_parameter' => '_password',
'domain_parameter' => '_domain',
'csrf_parameter' => '_csrf_token',
'intention' => 'authenticate',
'post_only' => true,
),
$options
),
$logger,
$dispatcher
);
}
/**
* Performs authentication.
*
* #param Request $request A Request instance
*
* #return TokenInterface|Response|null The authenticated token, null if full authentication is not possible, or a Response
*
* #throws AuthenticationException if the authentication fails
*/
protected function attemptAuthentication(Request $request)
{
// Create initial unauthenticated token and pass data to the authentication manager.
// TODO validate request data.
$username = trim($request->request->get($this->options['username_parameter'], null, true));
$password = $request->request->get($this->options['password_parameter'], null, true);
$domain = $request->request->get($this->options['domain_parameter'], null, true);
$token = $this->authenticationManager->authenticate(new UserToken($username, $password, $domain, $this->providerKey));
return $token;
}
}
The above code will invoke the the auth function on the provider via the AuthenticationManager:
//This is from the AuthenticationProvider
public function authenticate(TokenInterface $token) {
$loginHandler = new LoginAuthenticationHandler($token->getUsername(), $token->getPassword(), $token->getDomain());
//This code just calls our web service and authenticates. I removed some business logic here, but this shows the gist of it.
if(!$boAuthenticationToken = $loginHandler->authenticate())
{
throw new AuthenticationException('Bad credentials');
}
else{
$user = $this->userProvider->loadUserByUsername($token->getUsername());
//$user = $this->userProvider->getUser($token, $boAuthenticationToken);
// Set the user which will be invoked in the controllers.
$token->setUser($user);
$token->setAuthenticated(true);
return $token;
}
}
Bundle Services.yml
parameters:
services:
ws.security.authentication.provider:
#http://blog.vandenbrand.org/2012/06/19/symfony2-authentication-provider-authenticate-against-webservice/
class: Aurora\OurCustomBundle\Security\Authentication\Provider\Provider
arguments: ["bo_remove_this_with_bo_auth_service", "", "#security.user_checker", "", "#security.encoder_factory"]
ws.security.authentication.listener:
class: Aurora\OurCustomBundle\Security\Firewall\Listener
parent: security.authentication.listener.abstract
abstract: true
#arguments: []
arguments: ["#security.context", "#security.authentication.manager", "#security.authentication.session_strategy", "#security.http_utils", "ws.user_provider", "#security.authentication.customized_success_handler", "#main.cas.rest.user.authentication.failure.service"]
ws.user_provider:
class: Aurora\OurCustomBundle\Security\User\UserProvider
And lastly, the UserProvider
class UserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
//Just return a simple user for now.
return new User($username, array('ROLE_USER'));
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Aurora\OurCustomBundle\Security\User\User';
}
}
After many hours of hair pulling, I figured out the problem!
The token implementation was incorrect. Since I was implementing my own Token, which extends from AbstractToken, I needed to also implement the serialize() and unserialize() functions.
Once I did that, the code worked. The updated Token class is below for future reference:
class UserToken extends AbstractToken
{
private $username;
private $password;
private $domain;
private $providerKey;
public function __construct($username, $password, $domain, $provider_key, array $roles = array('ROLE_USER'))
{
parent::__construct($roles);
$this->username = $username;
$this->password = $password;
$this->domain = $domain;
$this->providerKey = $provider_key;
}
public function getCredentials()
{
return '';
}
function getUsername() {
return $this->username;
}
function getDomain() {
return $this->domain;
}
function getPassword() {
return $this->password;
}
function getProviderKey(){
return $this->providerKey;
}
function serialize(){
return serialize(array($this->username, $this->password, $this->domain, parent::serialize()));
}
function unserialize($serialized){
list($this->username, $this->password, $this->domain, $parentSerialization) = unserialize($serialized);
parent::unserialize($parentSerialization);
}
}