Version 5 supported

Integrating with other authenticators

If your project uses a non-standard authentication module, such as silverstripe/ldap, you will need to implement some customisations to connect the modules together. The following notes should serve as a guide for parts of the code to be aware of, and things to do in order to achieve this.

For the purposes of comparisons in this document, we will use silverstripe/ldap's authenticator.

Concepts

Configuration

The MFA module enables and sets itself as the default authenticator with config (see config.yml). You can leave this in place, but change the injected class to a custom class in your project with Injector configuration. See further down for a hypothetical example.

Authenticator

The Authenticator entrypoint class in the MFA module is SilverStripe\MFA\Authenticator\MemberAuthenticator. This class extends the default SilverStripe\Security\MemberAuthenticator class in order to override the default login form with LoginForm, and the change password handler with ChangePasswordHandler.

silverstripe/ldap does the same thing - it also configures itself to override the default authenticator. Since the MFA replacement for the default authenticator has MFA logic added to it, and LDAP has the same with LDAP logic added, you will need to reimplement it so that both MFA and LDAP apply their logic together.

In order to combine these two authenticators, you may choose to add your own LDAPMFAAuthenticator class and configure that instead of either MFA or LDAP's authenticators. See further down for a hypothetical example.

LoginHandler

The MFA LoginHandler class is the point where MFA flows are injected into core. In silverstripe/ldap, this class performs the same function: to inject LDAP authentication logic into core. As above, in order to have both work together you may choose to add your own LDAPMFALoginHandler class and configure that in your custom Authenticator.

This class would need to combine the logic from both SilverStripe\LDAP\Forms\LDAPLoginHandler and SilverStripe\MFA\Authenticator\LoginHandler in order to function correctly for both cases.

ChangePasswordHandler

Both the LDAP and MFA modules provide their own implementations of the ChangePasswordHandler, and in both cases these are referenced from the MemberAuthenticator subclass of each module. Similarly to the LoginForm example above, you will need to subclass and inject a custom implementation of this as well, combining both sets of logic.

Similarly to LoginForm above, in order to reduce duplication of code we recommend extending \SilverStripe\MFA\Authenticator\LoginHandler and duplicating the contents of SilverStripe\LDAP\Authenticators\LDAPChangePasswordHandler which is substantially smaller.

LoginForm and ChangePasswordForm

The LDAP module overrides a couple of the default Form implementations: LDAPLoginForm and LDAPChangePasswordForm form. The way that these classes are written likely indicates that there will not be any conflicts here with the MFA module, which does not extend these classes from core.

Examples

The following example classes should be enabled in your project with Injector configuration. For example:

SilverStripe\Core\Injector\Injector:
  SilverStripe\MFA\Authenticator\MemberAuthenticator:
    class: LDAPMFAAuthenticator

Note that this example overrides the default injection class for MemberAuthenticator, which will allow MFA's configuration to register the method and set it as the default authenticator to continue. If you have configured the LDAP authenticator you will want to remove this now - MFA configures itself automatically.

A custom MemberAuthenticator

// app/src/MFA/Authenticators/LDAPMFAMemberAuthenticator.php
namespace App\MFA\Authenticators;

use SilverStripe\LDAP\Authenticators\LDAPAuthenticator;
use SilverStripe\MFA\Authenticator\ChangePasswordHandler;
use SilverStripe\MFA\Authenticator\LoginHandler;

class LDAPMFAMemberAuthenticator extends LDAPAuthenticator
{
    public function getLoginHandler($link)
    {
        return LoginHandler::create($link, $this);
    }

    public function getChangePasswordHandler($link)
    {
        return ChangePasswordHandler::create($link, $this);
    }
}

In this example, we have copied the small amount of logic from the MFA module into this subclass, changed the parent class from the core MemberAuthenticator to LDAPAuthenticator, and will change the injection class name with the configuration above so it is used instead of MFA or LDAP.

A custom LoginHandler

In this example, the logic from silverstripe/ldap is much smaller, so is preferable to duplicate while extending the MFA LoginHandler which contains much more logic.

// app/src/MFA/Handlers/LDAPMFALoginHandler.php
namespace App\MFAHandlers;

use SilverStripe\LDAP\Forms\LDAPLoginForm;
use SilverStripe\MFA\Authenticator\LoginHandler;

class LDAPMFALoginHandler extends LoginHandler
{
    private static $allowed_actions = ['LoginForm'];

    public function loginForm()
    {
        return LDAPLoginForm::create($this, get_class($this->authenticator), 'LoginForm');
    }
}

A custom ChangePasswordHandler

As with the LoginHandler example above, the logic from silverstripe/ldap's ChangePasswordHandler is much smaller, so is used for this example.

// app/src/MFA/Handlers/LDAPMFAChangePasswordHandler.php
namespace App\MFA\Handlers;

use SilverStripe\LDAP\Forms\LDAPChangePasswordForm;
use SilverStripe\MFA\Authenticator\ChangePasswordHandler;

class LDAPMFAChangePasswordHandler extends ChangePasswordHandler
{
    private static $allowed_actions = [
        'changepassword',
        'changePasswordForm',
    ];

    public function changePasswordForm()
    {
        return LDAPChangePasswordForm::create($this, 'ChangePasswordForm');
    }
}