Authenticate User from Form Finisher

A bit of context

I’m using Neos.Form.Builder to define my forms with Fusion, and I’ve defined a new form finisher with the intent of reading the form values and authenticate the user via a UsernamePassword token. I’m also relying on a provider defined by Flowpack.Neos.FrontendLogin which, to my understanding, should match any request coming from the frontend.

The code

File: AuthenticateFinisher.php

<?php

namespace Vendor\Package\Finishers;

use Neos\Form\Core\Model\AbstractFinisher;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Security\Authentication\AuthenticationManagerInterface;
use Neos\Flow\Security\Exception\AuthenticationRequiredException;

use Neos\Flow\Security\Context;

class AuthenticateFinisher extends AbstractFinisher
{
    /**
     * @var AuthenticationManagerInterface
     * @Flow\Inject
     */
    protected $authenticationManager;
    
    /**
     * @var Context
     * @Flow\Inject
     */
    protected $securityContext;

    protected function executeInternal()
    {
        $formRuntime = $this->finisherContext->getFormRuntime();

        $formState = $formRuntime->getFormState();
        $usernameField = $this->options['usernameField'];
        $passwordField = $this->options['passwordField'];
        $username = $formState->getFormValue($usernameField);
        $password = $formState->getFormValue($passwordField);

        $request = $formRuntime->getRequest();
        $request->setArgument('__authentication.Neos.Flow.Security.Authentication.Token.UsernamePassword.username', $username);
        $request->setArgument('__authentication.Neos.Flow.Security.Authentication.Token.UsernamePassword.password', $password);

        $this->securityContext->setRequest($request);

        // try {
            $this->authenticationManager->authenticate();
        // } catch (AuthenticationRequiredException $exception) {
        //     // log ?
        // }

        if (!$this->authenticationManager->isAuthenticated()) {
            $this->finisherContext->cancel();
        }
    }
}

The Error

Could not authenticate any token.

Might be missing or wrong credentials or no authentication provider matched.
Exception Code	1222204027
Exception Type	Neos\Flow\Security\Exception\NoTokensAuthenticatedException
Log Reference	20200904080139a213ac
Thrown in File	Data/Temporary/Development/SubContextDocker/Cache/Code/Flow_Object_Classes/Neos_Flow_Security_Authentication_AuthenticationProviderManager.php
Line	188
Original File	Packages/Framework/Neos.Flow/Classes/Security/Authentication/AuthenticationProviderManager.php

The question

Apparently, I’m not fully grasping the concept of tokens and authentication.

I’ve tried to completely clear the security context and start anew with my fabricated ActionRequest, but that doesn’t work either.

I can confirm there are 3 providers at the moment: Neos.Neos:Backend, Neos.Setup:Login, and Flowpack.Neos.FrontendLogin:Frontend. On top of that, the Flowpack package defines a requestPattern on the backend and frontend providers, making them match each side accordingly.

As far as I know, that’s enough to trigger a token validation, which then should read the request and extract the internal arguments. However, that’s not going as expected.

How can I authenticate a user from a form finisher?

Any help would be appreciated.

Hi Federico and welcome to our community! :slight_smile:

Just to get a bit more context. Do you use the Neos.Form.Builder to create a simple login form? Or is it a signup/registration form and you want to authenticate the user afterwards? Or is it yet another scenario?

Hello Bastian,

I’ve defined a login form via Fusion (not through the interface) using the Neos.Form.Builder definitions. Here’s a simplified version of it:

prototype(Vendor.Package:Form.Login) < prototype(Neos.Form.Builder:Form) {
    presetName = 'default'
    identifier = 'login'

    firstPage {
        elements {
            email = Vendor.Package:Field.Email
            email.@position = 10

            password = Vendor.Package:Field.Password
            password.@position = 20
        }
    }
    finishers {
        authenticateFinisher = Vendor.Package:AuthenticateFinisher.Definition {
            options {
                usernameField = 'email'
                passwordField = 'password'
            }
        }
    }
}

I want to authenticate the users already associated with the Flowpack.Neos.FrontendLogin:Frontend provider. The sign up phase is carried through by a backend operator.

I’ve taken an alternative route now, and I’m trying to set the correct name on the two input fields in order to have a perfectly valid request on the first go. However, even though I manage to get the correct path out of the field identifier, the form name gets prepended with -- instead of __.

The new fusion file:

prototype(Vendor.Package:Form.Login) < prototype(Neos.Form.Builder:Form) {
    presetName = 'default'
    identifier = '__authentication'

    firstPage {
        elements {
            email = Vendor.Package:Field.Email {
                identifier = 'Neos.Flow.Security.Authentication.Token.UsernamePassword.username'
                @position = 10
            }

            password = Vendor.Package:Field.Password {
                identifier = 'Neos.Flow.Security.Authentication.Token.UsernamePassword.password'
                @position = 20
            }
        }
    }
    finishers {
        authenticateFinisher = Vendor.Package:AuthenticateFinisher.Definition
    }
}

The HTML output:

<!-- Note the name attribute -->
<input
    type="text"
    id="__authentication-Neos_Flow_Security_Authentication_Token_UsernamePassword_username"
    name="--__authentication[Neos][Flow][Security][Authentication][Token][UsernamePassword][username]"
>

I think this has a better chance of working.

How can I get rid of the -- output?

I searched through all the files of Neos.Form.Builder, Neos.Form, and Neos.FluidAdaptor, but to no avail.

I’ve manually tampered with the HTML document, and I can confirm this does work. Should I open a new thread on how to remove that unwanted prefix or can we continue here?

Unless otherwise stated, the default fieldNamePrefix of the FormViewHelper is the argument namespace. The FormRuntime (Neos\Form\Core\Runtime\FormRuntime:150) sets the argument namespace to --, followed by the form identifier.

To take control over the fieldNamePrefix, it must be explicitely passed as an attribute to the fluid tag. However, the default form template (resource://Neos.Form/Private/Form/Form.html) doesn’t take this option into account. Hence, I had to create a new form template and hard-code the field name prefix to __authentication.

The new fusion file:

prototype(Vendor.Package:Form.Login) < prototype(Neos.Form.Builder:Form) {
    presetName = 'default'
    formElementType = ${formNode.context.inBackend ? 'Neos.Form:FormEditMode' : 'Vendor.Package:LoginForm'}
    identifier = 'login'
    // ...
}

The additional configuration:

Neos:
  Form:
    presets:
      default:
        formElementTypes:
          'Vendor.Package:LoginForm':
            superTypes:
              'Neos.Form:Form': true

And finally, the form template (resource://Vendor.Package/Private/Form/LoginForm.html):

{namespace form=Neos\Form\ViewHelpers}
<form:form fieldNamePrefix="__authentication" action="index" object="{form}" method="post" id="{form.identifier}" section="{form.identifier}" enctype="multipart/form-data">
    <form:renderRenderable renderable="{form.currentPage}" />
    <div class="actions">
        <f:render partial="Neos.Form:Form/Navigation" arguments="{form: form}" />
    </div>
</form:form>

Hey,

sorry for not getting back to you earlier.

I was about to suggest that the Form Builder is a bit of an overkill for a login form.
Especially because the whole authentication handling requires some special handling.
But great that you have found a solution that works for you.

The $request->setArguments() part and especially $this->securityContext->setRequest() part is a bit flaky and might stop working in a future version of Neos.

The problem: With this approach your request is only “half” authenticated… Everything that is evaluated before the finisher has a different security context and this might lead to weird effects.

A safer way would be a custom Authentication Provider. Or – if you use a recent Flow version – reconfiguring the usernamePostField and passwordPostField of the PersistedUsernamePasswordProvider (see https://flowframework.readthedocs.io/en/stable/TheDefinitiveGuide/PartIII/Security.html#authentication-tokens).

In any case you should finish with a redirect (i.e. RedirectFinisher) so that the new request is fully authenticated

No problem, Bastian.

We’ve got many forms on this site, and I personally prefer fusion over fluid. That’s why I opted for Neos.Form.Builder. If it was just one login form, I’d definitely agree with you.

The finishers security context is a good observation. I’ll keep that in mind, and try to invoke it before everything else.

It’s also nice to know that I managed not to use a prone-to-change API, which means this solution won’t break in the near future.

Thank you for your time. Have a good one :raised_hands:t2:

1 Like

I’m afraid you misunderstood me, since I was basically claiming the opposite with

The […] part is a bit flaky and might stop working in a future version of Neos.

:grimacing:

Well, it is not said, that this part will stop working of course. But it’s certainly not part of the public API.

That will be difficult to achieve, but if you finish with a redirect it should not be an issue really.
And that’s something I’d recommend anyways.

Cheers, you too!