Extend MemberAuthenticator to allow more than one login identifier field

Silverstripe Version: 4.5.0

For legacy reasons, our users need to be able to log on to Silverstripe with either an email or a username. (the login form is default, but they can enter a username instead of an email address if they wish).
Reading https://docs.silverstripe.org/en/4/developer_guides/security/authentication/ the page says:

“If only a subset of the supportedServices() will be provided by the custom Authenticator, it is advised to extend SilverStripe\Security\MemberAuthenticator\MemberAuthenticator , as that default contains all required methods already and only an override or follow up needs to be written.”

That sounded ideal to me, as presumably I’d just have to write my own LoginHandler and everything else would function as normal?

So I added this to mysite.yml:

SilverStripe\Core\Injector\Injector:
  SilverStripe\Security\Security:
      properties:
        Authenticators:
          MyAuthenticator: '%$SilverStripe\Security\MemberAuthenticator\MyAuthenticator'

I then created :
app/src/Extensions/MyAuthenticator.php:

namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Core\Injector\Injector;
use Psr\Log\LoggerInterface;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;

class MyAuthenticator extends MemberAuthenticator {
    public function getLoginHandler($link)
    {
        return MyLoginHandler::create($link, $this);
    }
}

app/src/Extensions/MyLoginHandler.php:

namespace SilverStripe\Security\MemberAuthenticator;

use SilverStripe\Security\MemberAuthenticator\LoginHandler;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Control\HTTPResponse;
use SilverStripe\Control\RequestHandler;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Security\Authenticator;
use SilverStripe\Security\PasswordExpirationMiddleware;
use SilverStripe\Security\IdentityStore;
use SilverStripe\Security\Member;
use SilverStripe\Security\Security;
use SilverStripe\Core\Injector\Injector;
use Psr\Log\LoggerInterface;

class MyLoginHandler extends LoginHandler
{
	
    public function doLogin($data, MemberLoginForm $form, HTTPRequest $request)
    {
        // code in here to manipulate $data as needed
    }
}

All of the above worked, I was able to change the login behaviour via doLogin() so that normal email addresses login with no change, and usernames lookup what the email should be and substitute accordingly.

The problem I can’t work out how to solve is that our Login Form is now being output twice. It’s the default Silverstripe form, we haven’t written a custom one. It also has extra output at the top of each one that looks like this, which didn’t output prior to my changes:

<ul>
<li><a href="/Security/login#MemberLoginForm_LoginForm_Tab">E-mail &amp; Password</a></li> 
<li><a href="/Security/login#MemberLoginForm_LoginForm_Tab">E-mail &amp; Password</a></li>
</ul>

I went back and commented out all the code in MyAuthenticator.php, so it just reads:

namespace SilverStripe\Security\MemberAuthenticator;
use SilverStripe\Core\Injector\Injector;
use Psr\Log\LoggerInterface;
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;

class MyAuthenticator extends MemberAuthenticator {
}

then did a /dev/build/?flush=all and I get the same behaviour - so it looks to me that the injector addition to mysite.yml and declaring “class MyAuthenticator extends MemberAuthenticator” is what is causing our LoginForm to glitch.
If anyone can suggest what I’ve done wrong it would be very much appreciated.
Do I have problem with namespaces, is my approach fundamentally flawed? I’m a bit thrown to be honest as it’s like the hard part worked fine (extending the login logic) but I can’t work out how to debug the glitchy login form!

Thanks!

Do not put your stuff under the SilverStripe namespace: you are defeating the main purpose of namespacing! Instead put your stuff in your own namespace, or at the very least do not set the namespace at all.

In practice remove the lines starting with namespace SilverStripe and put MyAuthenticator: %$MyAuthenticator in your YAML.

Thank you very much for taking the time to reply to this. I think I tried namespacing out of desperation as my code wasn’t being called and I left it in once it was. Removing namespacing and using your syntax is much better.

I’m guessing the clue is in the yml key Authenticators (plural). It looks like you’ve added MyAuthenticator, but haven’t removed MemberAuthenticator. Try renaming the key MyAuthenticator in that yml file to default.

---
Name: MyAuth
After:
  - '#coresecurity'
---
SilverStripe\Core\Injector\Injector:
  SilverStripe\Security\Security:
    properties:
      Authenticators:
        default: %$MyVendor\MyProject\Authenticator\MyAuthenticator
1 Like

You are an absolute star for helping me with this, thank you!

Your guess was spot on, changing it to default means my login form only renders once now.
Additionally, I’d somehow managed to put my Injector snippet in the wrong place of mysite.yml - I just added it to previous injectors, rather than creating a dedicated After: #coresecurity section - doh!

1 Like