How to implement a custom TOTP-based 2FA solution for customer login, since Magento's native 2FA module only covers the admin area
Magento 2 has shipped a solid, native two-factor module for several versions, but it exclusively protects the admin area. No comparable built-in solution exists for customer accounts, even though stores with saved payment data, store credit balances, or sensitive B2B order data have a legitimate interest in additionally securing the storefront login. This article shows how to wire a custom TOTP-based two-factor authentication for customer accounts cleanly into the existing authentication flow, including the UX trade-off between mandatory and opt-in, plus a well thought out recovery code concept.
Table of Contents
- 1. Why Magento's native 2FA module only covers the admin area
- 2. Architecture of a custom customer 2FA solution
- 3. Implementing TOTP verification
- 4. UX trade-off: making 2FA mandatory or offering it as optional
- 5. Onboarding flow: activating 2FA step by step
- 6. Recovery codes: ensuring access without the lost device
- 7. Session handling and the remember device feature
- 8. An admin overview for customer service
- 9. Customer 2FA implementation steps at a glance
- 10. Summary
- 11. FAQ
1. Why Magento's native 2FA module only covers the admin area
The Magento_TwoFactorAuth module was originally introduced in response to a wave of admin account takeovers and is consistently tailored to the backend login: it hooks into the admin authentication flow, manages providers such as Google Authenticator or Duo Security, and offers its own admin UI to manage enabled factors. The entire structure of the module, from the database table down to the controllers, is strictly scoped to the adminhtml area.
There is architecturally no equivalent for the customer area, because Magento's core team historically classified the storefront login as lower risk than admin access, which potentially grants control over the entire store. That assessment no longer automatically holds once customer accounts themselves carry valuable data, such as saved payment methods, order histories with address data, or, in a B2B context, access to company wide price lists and order approval permissions.
2. Architecture of a custom customer 2FA solution
The clean entry point is a plugin on Magento\Customer\Model\AccountManagement::authenticate(), which fires after a successful password check but before the actual session gets established. Instead of logging the customer in immediately, an intermediate, time limited state is set once 2FA is enabled, signaling that the password was correct but the second factor is still pending.
The TOTP secret itself does not belong in the customer_entity table, but in a dedicated table with a foreign key to the customer ID, stored encrypted through Magento's EncryptorInterface. This separation prevents a dump of the standard tables from accidentally exposing 2FA secrets in plain text, and allows independent access control over the security critical data.
<!-- app/code/Mironsoft/CustomerTwoFactor/etc/db_schema.xml -->
<table name="mironsoft_customer_totp_secret" resource="default" engine="innodb">
<column xsi:type="int" name="customer_id" unsigned="true" nullable="false"/>
<column xsi:type="text" name="secret_encrypted" nullable="false"/>
<column xsi:type="smallint" name="is_enabled" unsigned="true" nullable="false" default="0"/>
<column xsi:type="timestamp" name="confirmed_at" nullable="true"/>
<constraint xsi:type="primary" referenceId="PRIMARY">
<column name="customer_id"/>
</constraint>
<constraint xsi:type="foreign" referenceId="MIRONSOFT_TOTP_CUSTOMER_ID"
table="mironsoft_customer_totp_secret" column="customer_id"
referenceTable="customer_entity" referenceColumn="entity_id" onDelete="CASCADE"/>
</table>
3. Implementing TOTP verification
TOTP, time based one time password, generates a six digit code from a shared secret and the current time, typically changing every thirty seconds. On the server side, Magento computes the same code from the stored secret and compares it against the user's input, tolerating a small window of one step forward and back to absorb clock drift between server and authenticator app.
The actual TOTP computation should not be hand rolled but pulled in through a vetted library, since subtle mistakes in base32 decoding the secret or in the HMAC computation can undermine the entire security of the scheme. The intermediate state from the authentication plugin only gets promoted to a full customer session after successful code verification.
<?php
declare(strict_types=1);
namespace Mironsoft\CustomerTwoFactor\Model;
/**
* Verifies a customer-entered TOTP code against the stored secret.
*/
final class TotpVerifier
{
private const TIME_STEP_SECONDS = 30;
private const ALLOWED_DRIFT_STEPS = 1;
/**
* Checks whether the entered code is valid within the tolerance window.
*
* @param string $secret Base32-encoded TOTP secret
* @param string $code Six digit code entered by the customer
* @return bool
*/
public function verify(string $secret, string $code): bool
{
$currentStep = (int) floor(time() / self::TIME_STEP_SECONDS);
for ($drift = -self::ALLOWED_DRIFT_STEPS; $drift <= self::ALLOWED_DRIFT_STEPS; $drift++) {
if (hash_equals($this->generateCode($secret, $currentStep + $drift), $code)) {
return true;
}
}
return false;
}
}
4. UX trade-off: making 2FA mandatory or offering it as optional
Mandatory 2FA for every customer account noticeably improves security, but it also produces a measurable increase in login drop-off and additional support load from customers who lost their phone or need to set up the authenticator app again. For a typical B2C store with low average cart value, a mandatory second factor on every single login is often disproportionate to the actual risk.
A proven middle ground is a risk based, opt-in oriented approach: 2FA gets offered as an optional but visibly promoted setting in the customer account, while becoming mandatory in specific contexts, such as before changing the shipping address on an active order, before adding a new payment method, or generally for B2B company accounts with order approval permissions. That context dependent requirement can be hooked into the same plugin architecture at targeted points in checkout and account management, instead of tying it flatly to login alone.
5. Onboarding flow: activating 2FA step by step
The activation process usually starts with the server generating a new TOTP secret, displayed as a QR code so the customer can conveniently scan it with their authenticator app. It matters not to mark the secret as active at this point, only holding it temporarily instead, since the customer may not have successfully set up the app yet.
Only once the customer correctly enters a currently valid six digit code from their freshly configured app in the second step does the secret get permanently marked as active and the recovery codes get shown once. That confirmation step prevents a customer from accidentally locking themselves out with a wrongly scanned or non functional secret, since a working roundtrip has to be proven before 2FA actually becomes mandatory for future logins.
6. Recovery codes: ensuring access without the lost device
Without a recovery path, a customer effectively locks themselves out of their own account the moment they lose their phone, which inevitably generates support tickets. The standard approach is recovery codes: a fixed number of single use codes shown once when 2FA gets enabled, offered for printing or secure storage.
Each recovery code must technically work only once and needs to be marked as consumed immediately after use, stored as a hash rather than plain text, exactly like the password itself. Once the number of remaining codes drops below a threshold, say two out of an original ten, the customer should be proactively prompted to generate a new set on their next successful login, so they never end up completely without a fallback unnoticed.
7. Session handling and the remember device feature
A second factor on absolutely every login improves security but noticeably annoys customers on frequently used, trusted devices. A remember device feature stores a signed, time limited token in the customer's browser after successful 2FA verification, which skips the second factor for a defined period, typically thirty days, on future logins from the same device.
This token must never replace the password, only skip the 2FA prompt, and should be bound to device characteristics such as the user agent while remaining revocable server side, for instance when the customer signs out of all active sessions in their account settings. Without that revocation option, a stolen remember device token would stay valid indefinitely, even after the customer changed their password.
8. An admin overview for customer service
So support can help customers who lost their device and ran out of recovery codes, a dedicated admin grid view is needed showing the 2FA status per customer account, along with an action to controllably reset the 2FA configuration after verifying the customer's identity through the regular support channel.
That reset action must be logged without exception, including the admin user who triggered it, because resetting 2FA effectively creates a second, standalone authentication weak point should an attacker try to manipulate the support process itself, for instance through social engineering.
9. Customer 2FA implementation steps at a glance
The table below summarizes the central building blocks of a custom customer 2FA solution and their respective role.
| Building Block | Responsible Component | Role | Security Aspect |
|---|---|---|---|
| Login intermediate state | Plugin on AccountManagement::authenticate | Establish session only after the second factor | Prevents a full login with password alone |
| TOTP secret storage | Dedicated table with EncryptorInterface | Encrypted storage separate from customer_entity | No plain text secret in a database dump |
| Code verification | TotpVerifier with time window tolerance | Checking the six digit code against the secret | Vetted library instead of a hand rolled implementation |
| Recovery codes | Hashed single use codes | Ensuring access on device loss | Each code usable only once |
| Remember device token | Signed, revocable token | Skipping the 2FA prompt on trusted devices | Revocable server side on password change |
Mironsoft
Magento development, module consulting, and system architecture
A Magento project that needs a second opinion or experienced execution?
We build custom Magento modules, advise on architecture decisions, and take on complex implementations, from service contract planning to production-ready deployment.
Architecture Consulting
Have module and system architecture thought through properly before you build.
Custom Module Development
Build custom Magento modules cleanly, following best practices.
Code Review & Audit
Have existing modules reviewed for performance, security, and maintainability.
10. Summary
Customer 2FA: The Essentials at a Glance
Core idea
Magento's native 2FA module only covers the admin, customer 2FA needs a dedicated, clean implementation.
Central entry point
A plugin on AccountManagement::authenticate before the actual session gets established.
Biggest UX risk
Mandatory 2FA on every login without a remember device feature noticeably raises login drop-off.
Success criterion
Recovery codes and a logged support reset prevent permanent lockout without creating a new attack surface.