I have installed the packages Google-mailer, doctrine -fixtures-bundle, and webpack-bundle. I'm at the beginning of this project and there to create the first controller which should have a form to login. Controller and From were created with the commands bin/console make: controller and make: form, and not changed. But I already have a problem because I get an error when I call the form
Failed to start the session: already started by PHP.
I can explain this message with the fact that when creating the view Symfony tries to start a session because the form is protected with csrf (which should stay that way). But this should not be a problem because Symfony manages the sessions.
php.ini => session.auto_start=0;
framework.yaml:
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
#http_method_override: true
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
#esi: true
#fragments: true
php_errors:
log: true
Test function in HomeController:
/**
* #Route("/testform", name="testform", methods={"GET","POST"})
*/
public function testform()
{
$User = new User();
$form = $this->createForm(TestFormType::class, $User);
$form->handleRequest($this->request);
if ($form->isSubmitted() && $form->isValid()) {
return $this->redirectToRoute('home');
}
return $this->render('home/testform.html.twig', [
'form' => $form->createView(),
]);
}
TestFormType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('forname')
->add('name')
->add('password');
}
Related
I am trying to create a logout feature which when clicked from the below (example) redirects to the necessary page.
Example:
If anybody clicks logout with website beginning with aaa.io/user -> go to aaa.io/login.
If anybody clicks logout with website beginning with aaa.io/dev/{id} -> go to aaa.io/home/{id}
How to create two logout feature which will redirect to two seperate pages? I have tried with first example and works fine.I heard we can do it using Symfony firewall but unable to get it.
#security.yaml
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
App\Entity\Dimitry:
algorithm: auto
providers:
app_user_provider:
entity:
class: App\Entity\Dimitry
property: email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
custom_authenticator: App\Security\LoginAuthenticator
logout:
path: app_logout
target: app_login
//Security Controller
#[Route(path: '/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response
{
if ($this->getUser()) {
return $this->redirectToRoute('app_home');
}
// 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: 'app_logout')]
public function logout()
{
return $this->redirectToRoute('app_login');
//throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
}
The logout method is never called because it's intercepted by the logout key on your firewall. So the code lines in the public function logout won't be executed.
IMO, you could use events :
Create a subscriber,
Subscribe on LogoutEvent::class,
Analyze the request provided by the logout event
Use it to determine the route,
Catch the response provided by the logout event,
Use the UrlGenerator to redirect user,
Update the response to redirect to the corresponding route
Documentation provides a very good example, you can use as a template for your logic. Your subscriber could be like this one:
// src/EventListener/LogoutSubscriber.php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Http\Event\LogoutEvent;
class LogoutSubscriber implements EventSubscriberInterface
{
public function __construct(
private UrlGeneratorInterface $urlGenerator
) {
}
public static function getSubscribedEvents(): array
{
//2 - Subscribe to LogoutEvent
return [LogoutEvent::class => 'onLogout'];
}
public function onLogout(LogoutEvent $event): void
{
// get the security token of the session that is about to be logged out
$token = $event->getToken();
// 3. get the current request
$request = $event->getRequest();
// 4. Your own logic to analyze the URL
$route = 'homepage';//default route
if (...) {
$route = 'URL1';
}
if (...) {
$route = 'URL2';
}
// 5. get the current response, if it is already set by another listener
$response = $event->getResponse();
// configure a custom logout response to the homepage
$response = new RedirectResponse(
$this->urlGenerator->generate($route),
RedirectResponse::HTTP_SEE_OTHER
);
$event->setResponse($response);
}
}
I have a Symfony project with FOSUserBundle, i extended the FormType in my personal Bundle following this guide "http://symfony.com/doc/master/bundles/FOSUserBundle/overriding_forms.html"
I created a Person entity with first name, last name, adresse ..., created its FormType like this :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstName', TextType::class)
->add('lastName', TextType::class)
->add('adress', TextType::class)
->add('account', AccountType::class) ;
}
The Account entity is the user class for FOSUserBundle
Then i generated the CRUD for Person, the newAction() looks like this :
public function newAction(Request $request)
{
$person = new Person();
$form = $this->createForm('AppBundle\Form\PersonType', $person);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$person->getAccount()->setEnabled(1); //i'm doing this because it's not automatic
$em->persist($person->getAccount());
$em->persist($person);
$em->flush($person);
return $this->redirectToRoute('fos_user_security_login'); // redirecting to the login page because it's not done automatically
}
return $this->render('person/new.html.twig', array(
'person' => $person,
'form' => $form->createView(),
));
}
The two entitys have a OneToOne relationship, with this code they are both persisted in the data base and i can use the username and password to log in normally
Here is my FOSUerBundle configuration :
fos_user:
db_driver: orm
firewall_name: main
user_class: AppBundle\Entity\Account
from_email:
address: "%mailer_user%"
sender_name: "%mailer_user%"
registration:
form:
type: AppBundle\Form\AccountType
I want to the user to log in automatically after registration like it happens when using the default FOSUserBundle registration, anyone got an idea how to do so ?
I tried a lot of stuff but no success
Thanks for your answers
I was able to login from my controller using the $person->getAccount() object:
All i have to do is dispatch the REGISTRATION_COMPLETED event like in the default Controller : https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Controller/RegistrationController.php#L78
Just copied some code to my controller
This will trigger the authentication : https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/EventListener/AuthenticationListener.php
i got a very strange issue looking like this:
[2014-11-06 11:21:13] request.INFO: Matched route "core_timetracking_new_user" (parameters: "_controller": "Bricks\Custom\CoreBundle\Controller\TimeTrackingController::newuserAction", "_route": "core_timetracking_new_user") [] []
[2014-11-06 11:21:13] request.CRITICAL: Uncaught PHP Exception RuntimeException: "Failed to start the session: already started by PHP." at /var/cache/app/prod/classes.php line 113 {"exception":"[object] (RuntimeException: Failed to start the session: already started by PHP. at /var/cache/app/prod/classes.php:113)"} []
the strange thing is i do not start a session or use it, heres the Controller code:
/**
* #View
*/
public function newuserAction()
{
$trackingService=$this->get('core.timetracking_service');
$user= new TimeTrackingUser();
$request=$this->getRequest();
$form = $this->createFormBuilder($user)
->add('name','text')
->add('email','text')
->add('pass','password')
->add('save', 'submit', array('label' => 'Erstellen'))
->getForm();
$form->handleRequest($request);
if ($form->isValid()) {
$trackingService->persistUser($form->getData());
return $this->redirect($this->generateUrl('core_timetracking_user_list'));
}else {
return array(
'form' => $form->createView()
);
}
}
while this action works wonderfull
/**
* #View
*/
public function listuserAction()
{
$request=$this->getRequest();
$trackingService=$this->get('core.timetracking_service');
$users=$trackingService->getAllUsers();
return array(
'users' => $users
);
}
so the only difference is that i use
$form->handleRequest($request);
also checked if all my files AppKernel etc do start with
both actions ( the working one and the not working one ) are in the same controller
As soon as you render a form, Symfony automatically starts a session to store the token for CSRF Protection:
http://symfony.com/doc/current/book/forms.html#csrf-protection
You can disable CSRF Protection, but it's on by default.
#rakete:
The only additional idea I have is to change the way in which session files are stored (e.g. file system, database, memory, etc.). See here: http://symfony.com/doc/current/components/http_foundation/session_configuration.html
You should check if you have any listener who start a new session.
You mustn't have a listener with a new Session(). You should use the request session as in the action methods.
I have a onKernelController listener who started a new session with new Session() and then when the form try to make the csrf token it checks if a session exists and throw the exception.
I have an example where I am trying to create an AJAX login using Symfony2 and FOSUserBundle. I am setting my own success_handler and failure_handler under form_login in my security.yml file.
Here is the class:
class AjaxAuthenticationListener implements AuthenticationSuccessHandlerInterface, AuthenticationFailureHandlerInterface
{
/**
* This is called when an interactive authentication attempt succeeds. This
* is called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* #see \Symfony\Component\Security\Http\Firewall\AbstractAuthenticationListener
* #param Request $request
* #param TokenInterface $token
* #return Response the response to return
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => true);
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
/**
* This is called when an interactive authentication attempt fails. This is
* called by authentication listeners inheriting from
* AbstractAuthenticationListener.
*
* #param Request $request
* #param AuthenticationException $exception
* #return Response the response to return
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
$result = array('success' => false, 'message' => $exception->getMessage());
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
}
}
This works great for handling both successful and failed AJAX login attempts. However, when enabled - I am unable to login via the standard form POST method (non-AJAX). I receive the following error:
Catchable Fatal Error: Argument 1 passed to Symfony\Component\HttpKernel\Event\GetResponseEvent::setResponse() must be an instance of Symfony\Component\HttpFoundation\Response, null given
I'd like for my onAuthenticationSuccess and onAuthenticationFailure overrides to only be executed for XmlHttpRequests (AJAX requests) and to simply hand the execution back to the original handler if not.
Is there a way to do this?
TL;DR I want AJAX requested login attempts to return a JSON response for success and failure but I want it to not affect standard login via form POST.
David's answer is good, but it's lacking a little detail for newbs - so this is to fill in the blanks.
In addition to creating the AuthenticationHandler you'll need to set it up as a service using the service configuration in the bundle where you created the handler. The default bundle generation creates an xml file, but I prefer yml. Here's an example services.yml file:
#src/Vendor/BundleName/Resources/config/services.yml
parameters:
vendor_security.authentication_handler: Vendor\BundleName\Handler\AuthenticationHandler
services:
authentication_handler:
class: %vendor_security.authentication_handler%
arguments: [#router]
tags:
- { name: 'monolog.logger', channel: 'security' }
You'd need to modify the DependencyInjection bundle extension to use yml instead of xml like so:
#src/Vendor/BundleName/DependencyInjection/BundleExtension.php
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
Then in your app's security configuration you set up the references to the authentication_handler service you just defined:
# app/config/security.yml
security:
firewalls:
secured_area:
pattern: ^/
anonymous: ~
form_login:
login_path: /login
check_path: /login_check
success_handler: authentication_handler
failure_handler: authentication_handler
namespace YourVendor\UserBundle\Handler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Bundle\FrameworkBundle\Routing\Router;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
AuthenticationFailureHandlerInterface
{
private $router;
public function __construct(Router $router)
{
$this->router = $router;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// If the user tried to access a protected resource and was forces to login
// redirect him back to that resource
if ($targetPath = $request->getSession()->get('_security.target_path')) {
$url = $targetPath;
} else {
// Otherwise, redirect him to wherever you want
$url = $this->router->generate('user_view', array(
'nickname' => $token->getUser()->getNickname()
));
}
return new RedirectResponse($url);
}
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
if ($request->isXmlHttpRequest()) {
// Handle XHR here
} else {
// Create a flash message with the authentication error message
$request->getSession()->setFlash('error', $exception->getMessage());
$url = $this->router->generate('user_login');
return new RedirectResponse($url);
}
}
}
If you want the FOS UserBundle form error support, you must use:
$request->getSession()->set(SecurityContext::AUTHENTICATION_ERROR, $exception);
instead of:
$request->getSession()->setFlash('error', $exception->getMessage());
In the first answer.
(of course remember about the header: use Symfony\Component\Security\Core\SecurityContext;)
I handled this entirely with javascript:
if($('a.login').length > 0) { // if login button shows up (only if logged out)
var formDialog = new MyAppLib.AjaxFormDialog({ // create a new ajax dialog, which loads the loginpage
title: 'Login',
url: $('a.login').attr('href'),
formId: '#login-form',
successCallback: function(nullvalue, dialog) { // when the ajax request is finished, look for a login error. if no error shows up -> reload the current page
if(dialog.find('.error').length == 0) {
$('.ui-dialog-content').slideUp();
window.location.reload();
}
}
});
$('a.login').click(function(){
formDialog.show();
return false;
});
}
Here is the AjaxFormDialog class. Unfortunately I have not ported it to a jQuery plugin by now... https://gist.github.com/1601803
You must return a Response object in both case (Ajax or not). Add an `else' and you're good to go.
The default implementation is:
$response = $this->httpUtils->createRedirectResponse($request, $this->determineTargetUrl($request));
in AbstractAuthenticationListener::onSuccess
I made a little bundle for new users to provide an AJAX login form : https://github.com/Divi/AjaxLoginBundle
You just have to replace to form_login authentication by ajax_form_login in the security.yml.
Feel free to suggest new feature in the Github issue tracker !
This may not be what the OP asked, but I came across this question, and thought others might have the same problem that I did.
For those who are implementing an AJAX login using the method that is described in the accepted answer and who are ALSO using AngularJS to perform the AJAX request, this won't work by default. Angular's $http does not set the headers that Symfony is using when calling the $request->isXmlHttpRequest() method. In order to use this method, you need to set the appropriate header in the Angular request. This is what I did to get around the problem:
$http({
method : 'POST',
url : {{ path('login_check') }},
data : data,
headers: {'X-Requested-With': 'XMLHttpRequest'}
})
Before you use this method, be aware that this header does not work well with CORS. See this question
I am attempting to override the RegistrationFormType in the Symfony2 FOSUserBundle. I am following the documentation and believe i've covered everything. I've created a bundle to contain my overrides to the FOSUserBundle and the following code is from this bundle as well as the application config.
Has anyone experienced this when overriding FOSUserBundle, or see anything in my code that would help explain why I keep getting this error.
I'm on symfony v2.0.4
RegistrationFormType.php
<?php
/*
* This file is part of the FOSUserBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Thrive\SaasBundle\Form\Type;
use Symfony\Component\Form\FormBuilder;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
class RegistrationFormType extends BaseType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('firstname', null, array('error_bubbling' => true))
->add('lastname', null, array('error_bubbling' => true))
->add('company', null, array('error_bubbling' => true))
->add('email', 'email', array('error_bubbling' => true))
->add('username', null, array('error_bubbling' => true))
->add('plainPassword', 'repeated', array('type' => 'password', 'error_bubbling' => true))
;
}
public function getName()
{
return 'thrive_user_registration';
}
}
Services.yml
services:
thrive_saas_registration.form.type:
class: Thrive\SaasBundle\Form\Type\RegistrationFormType
arguments: [%fos_user.model.user.class%]
tags:
- { name: form.type, alias: thrive_user_registration}
Application's Config File
fos_user:
...
registration:
form:
type: thrive_user_registration
Turns out my services.yml file wasn't being loaded via dependency injection. After digging around i realized my extension.php file for this bundle was named incorrectly. Early on I had renamed the bundle and made a typo when renaming the extension.php file inside the DependencyInjection folder. After correcting the mispelling everything works again.
Did you tried to just add one new field and look if it works?
public function buildForm(FormBuilder $builder, array $options)
{
parent::buildForm($builder, $options);
// add your custom field
$builder->add('name');
}
Also remember to clear your prod cache if you're testing from there...