This years christmas gift, the TYPO3 extension "make", is a kickstarter CLI tool for various TYPO3 functionalities.
You may have already heard about multi-factor authentication (MFA), which is sometimes also referenced as 2FA—two-factor authentication. The idea is having multiple—additional—factors for authentication. So, not longer only the common username + password combination. While this idea is not new, the initial RFC dates back to December 2005, it took some time until systems were implementing the specification. TYPO3 now joins in by introducing MFA with version 11.1, even though extensions exist providing specific MFA solutions for prior TYPO3 releases.
The main reason why TYPO3 took quite a bit of time for the actual implementation, is mainly based on the fact that already existing Authentication APIs of TYPO3 needed to be adapted. Since this is a security-critical area, it required a lot of conceptual work as well as testing.
In this post, I want to explain the details of my journey from the idea to the actual implementation.
We first of all had to define the necessary requirements, before writing any line of code.
The Authentication Services API is surely one of the oldest APIs in the TYPO3 Core. It is used to process any login attempt to the Backend by fetching a user record and verifying the provided credentials. This is especially useful when integrating third-party services like LDAP. It is clear that it wouldn’t be a good idea to change such a widely used API, which has been working quite reliably for everyone since its initial implementation. Therefore, we decided to build MFA on top of the already existing Authentication mechanism. This brought us a lot of benefits. The most important one: MFA can be used independent of any preceding authentication step. We therefore also decided to name the new implementation MFA “providers” instead of “services”, to distinguish between these two concepts.
Another great benefit of having MFA implemented on top of the authentication stack is that after the user was successfully verified by the authentication services, we already have the corresponding user record available. So we are able to check the record for MFA being required and if this requirement is not fulfilled yet. In this case the user gets forward to the new multi-factor authentication endpoint as long as the requirement is not successfully satisfied. In case the authentication was successful or the user did not activate MFA at all, a redirect to the Backend will be performed.
To meet the goal of having a straightforward API, allowing a wide range of different providers, a generic approach had to be used. Therefore, the only requirement for integrating a MFA provider is to implement a new interface, the
MfaProviderInterface. This interface defines all necessary functionality such as the activation and verification, next to some so-called “state-providing” functionalities, e.g.
isLocked(). This way, TYPO3 does not need to know anything about the actual provider specific implementation but can just ensure the necessary functionality exists and can be called from e.g. a controller. Whenever TYPO3 calls a MFA provider, all necessary information such as the PSR-7 request object or the current user record, will be forwarded.
A provider is not required to store any data inside TYPO3 (e.g. the database). This allows full flexibility, since a provider can also use an external source for retrieving necessary information like the users’ MFA status. Nevertheless, the MFA API also features the possibility to store provider specific data in a new database field in the corresponding user record. This simplifies the whole process a lot, since e.g. the current context—BE or FE—is implicit through the received user record. Since the new user records’ field contains the specific data of all providers, using this functionality, they are stored as a JSON string. To access and manipulate these data in a comfortable way, the new
MfaProviderPropertyManager should be used. This component ensures data integrity and also takes care of internal tasks like updating the associated user object during runtime.
While it would be handy to have a MFA provider registered just by implementing the
MfaProviderInterface, this would also bring some drawbacks. On the one hand, an administrator would not be able to deactivate any registered MFA provider and furthermore no configuration, like managing the providers ordering, would be possible. Therefore, we decided to use the symfony service container configuration functionality, which is common in TYPO3 since its introduction in version 10. Hence, each provider is configured as a service in either the
Services.php file of the extension. The service has to be tagged with the
mfa.provider tag, in order to be available as a MFA provider. The tag can receive further tag arguments, e.g. the providers title or the ordering. These arguments are then evaluated in the compiler pass, running through all those tagged services.
With version 11.1, TYPO3 already integrates MFA into the TYPO3 Backend. This is achieved with the new MFA configuration endpoint, available via the User->Setup module.
The controllers’ task is to bundle all registered providers and make them accessible for the user to interact with. It does, besides some configurable information like requiring the user to set up MFA, not include any content by itself. The displayed information is gathered by requesting the corresponding providers, through the methods, defined by the
This also means, each user interaction, e.g. unlocking a provider, won’t be processed by the module but instead directly forwarded to the appropriate provider, more precisely to the defined action method, e.g.
The same applies to the interaction views, where the user has to enter MFA provider specific information, e.g. a one-time password for setting up the TOTP provider. The module only provides the “frame” while the actual content, e.g. the input fields, have to be added by the MFA provider. Whenever a user enters data in such an interaction view, these data are directly passed - unchanged - to the corresponding provider. The module just requires the MFA provider to notify about the result. Whether the performed action was successful or not. This allows the module to inform the user in a standardized way.
Please note: As of today, the API, especially the
handleRequest() method, used by the MFA providers to add their custom fields for interaction views, is still experimental. So it’ll may see adaptations in upcoming releases.
As mentioned, TYPO3 version 11.1 already supports MFA in the TYPO3 Backend. The API is built in a way for all MFA providers to automatically support both, the backend and the frontend. So, the only dependency is a corresponding Core module which supplies the interaction views for the user and forwards the requests to the MFA provider methods, defined in the
MfaProviderInterface. It is then the MFA providers’ task to differentiate between a backend and a frontend user.
As you may already expect: The API—namely the
MfaProviderPropertyManager—takes care of modifying the correct user’s authentication state for both contexts. However, there are surely some cases where it makes sense for the MFA provider to distinguish between the current context. An example would be the HTML template, used for providing the user interaction fields, as a provider could use Twitter Bootstrap related classes while this framework might not be available in the frontend.
Besides the general MFA functionality, the Backend implementation comes with a couple of specific features. For example, an administrator is able to limit the allowed providers for a user or a user group, require MFA being set up or define a recommended provider. Furthermore did we adjust some related parts in existing backend modules. The backend user module displays the current MFA status of each user and the configuration module lists all registered MFA providers. Also the user record was extended for a new field, allowing an administrator to manage the users activated MFA providers.
While there are a lot of possible MFA providers out there, we decided to start with the so-called “Time-based one-time password provider” (TOTP), since it is surely one of the most common ways for the additional authentication and therefore already well-known by a wide range of users.
For this method, the user just needs to scan a QR-code with a compatible application. The application will then generate one-time passwords, each valid for a short period of time, based on the negotiated shared secret. To read more about how this works in detail, have a look at the corresponding RFC.
We furthermore needed a fallback for the situation when a user does not longer have physical access to the application, generating the one—time passwords, if for example the corresponding device is lost. A common fallback mechanism is the use of so-called “recovery—codes”, sometimes called “backup—codes”. The functionality is quite simple. The user receives a set of codes and whenever needed, one of them can be used for authentication instead of the main MFA provider, e.g. TOTP.
It’s important to note that the recovery—codes provider is not bound to the TOTP provider. It’s an independent MFA provider, with the only restriction that it can’t be activated or used in case no other MFA provider is active at this time. This means, the recovery-codes provider can not be used as the “primary” provider.
Because of this fact, it’s not possible to enter a recovery code in the TOTP specific authentication view. Therefore, we implemented a mechanism which allows the user to switch between his activated providers on each authentication attempt. This makes it possible to not only switch between a “primary” provider and a fallback provider but also allows switching between multiple “primary” providers. This is especially useful in case a user e.g. uses TOTP on-the-go and a hardware token in the office.
Implementing MFA in TYPO3 was very time consuming, so it is one of the features that are hard to implement in ones’ spare time. Luckily, the TYPO3 GmbH and the TYPO3 Security Team decided to sponsor this feature, to finally have MFA as a first-class citizen in TYPO3 Core.
However, there is still a lot to be done—I now have a lot more ideas. So in case you are interested in the further development, please let me know either directly or by joining the
#mfa-in-core slack channel.