I have a Laminas MVC (v3) Application and noticed a serious issue with the CSRF Validation.
While starting to run this application on more than on PHP Server in parallel, all CSRF Validations failed to 50% … makes sense so far, because i didn’t configure the session storage and therefor used the native Savehandler. This, of cause, only works in a single-server environment.
Solution felt easy: I set up a different SaveHandler via this Documentation that stores the session data in a central Redis instance. So far, so good.
BUT: The CSRF Validator doesn’t use this Configuration unless i specify the Container (option ‘session’) to each and every CSRF Validator (or Element) manually. As I’m using shorthand configuration (see below), this would be a pain in the A** to change all the Forms and provide specific instanciated Elements/Validators.
Is there a way to tell the application to always use the configured Session Container. I would like to use the correct (“global”) Session Container / Config without touching every single Form.
Form example:
class SignUpForm extends \Laminas\Form\Form
{
public function init()
{
$this->add([
'type' => Element\Email::class, // Custom Form Element
]);
$this->add([
'type' => Element\FirstName::class, // Custom Form Element
]);
$this->add([
'type' => Element\LastName::class, // Custom Form Element
]);
$this->add([
'type' => Csrf::class, // \Laminas\Form\Element\Csrf
'name' => 'csrf',
]);
}
}
You can create a factory for the form element Csrf. Here is an example:
namespace ExampleModule\Form;
use Laminas\Form\Element\Csrf;
use Laminas\Session\Container as SessionContainer;
use Psr\Container\ContainerInterface;
final class CsrfFactory
{
public function __invoke(ContainerInterface $container): Csrf
{
/** @var SessionContainer $sessionContainer */
$sessionContainer = $container->get(SessionContainer::class);
$element = new Csrf();
$element->setCsrfValidatorOptions(
[
'session' => $sessionContainer,
]
);
return $element;
}
}
Then extend the configuration and use the new factory for the existing form element. Example:
Caution: There is a problem with this example and the current version of laminas-form, because if the option csrf_options is used for the element Csrf in a form class, the session container is overwritten!
If I think of something else or better, I will add it here.
It is always the same: if there are already objects/plugins like validators, filters, form elements, controller plugins, hydrators, cache storage adapters, translation loaders, view helpers, feed extensions, input filters, etc. then a plugin manager is used.
This means that the service container of your application is not used for these plugins, because most components can be used as stand-alone and you do not have a global service container there.
If you do not know or do not find the correct configuration key to register custom plugins then check the factory of the related plugin manager of the component. Here are some examples:
OT: I always stumble over these PluginManagers. Not quiet transparent, when the ServiceManager is used and when a speicific PluginManager, imho.
Agreed, this was initially done to scope the various services, but ultimately, it mostly led to a lot of confusion for most users.
This is something that the Laminas TSC should certainly discuss in future, to come up with a better design, especially now that PSR-11 is more popular.
Unfortunately, it was never documented either, especially that it is the same thing over and over again and can be used the same way. This means that the big picture is missing for the user.
As someone that is relatively new to Laminas. This was one of the main hurdles. I actually wrote my own plugin manager just to learn exactly how they functioned and why. Now, that I understand them, it makes perfect sense. When you’re new and first trying to get your head around it… An in-depth look into the how and why would have been wonderful.