Customer Two-Factor Authentication
Protect customer accounts with a second sign-in step: after the password, customers enter a 6-digit code from an authenticator app (with QR code setup) or from a text message.
Check it fits your store
Questions about compatibility? Contact us.
Details
Customer Two-Factor Authentication adds a second step to your customers' sign-in. After the password, the customer enters a 6-digit code from an authenticator app (Google Authenticator, Microsoft Authenticator, 1Password, Authy and other time-based code apps) or from a text message. A stolen or reused password alone no longer opens the account. Magento's own two-factor authentication protects admin users only; this extension is for storefront customers and works next to it.
How customers sign in
- Every storefront sign-in waits for the code: the Sign In page, AJAX sign-in popups, checkout sign-in, social sign-in and email confirmation links. The customer is not signed in until the code is right.
- The code page checks the code as soon as the last digit is typed or pasted (6 digits, or 8 characters for a backup code). Verify stays for keyboards and screen readers.
- "Don't ask again on this device" remembers a device for a number of days you choose. The device is kept on the server, so it is also recognised by its browser after the customer cleared cookies (setting: same browser, same browser and same network, or only with the cookie).
- Other ways to sign in on the same page: the other method, a backup code, or cancelling the sign-in. After too many wrong codes the sign-in ends; with Customer Account Blocks 1.3.0 or later, the account can be blocked.
Setup in My Account
- My Account > Two-Factor Authentication: scan a QR code (the optional endroid/qr-code library, installed with Magento's admin two-factor authentication) or type the setup key, then confirm with a code.
- Text messages through Twilio, Vonage, Plivo, Sinch, Infobip, Telnyx, Prelude or any SMS provider with an HTTP API, with hourly and daily limits and a test message from the settings page.
- Backup codes shown once, with Download (.txt), Copy and Print; stored as hashes only.
- A list of remembered devices with Forget All Devices, the choice of the method asked first, and turning methods off with the password.
- An optional mobile number with consent on the Create an Account page.
Consent to text messages
No text message is sent without consent. An unticked consent box with your disclosure sits next to every mobile number field; every consent is saved with the number, page, exact text, IP address, browser and time, and ends when the customer turns text messages off, the number changes, the store resets it or the provider reports a STOP reply. This is the record US carriers ask for in A2P 10DLC registration.
Getting customers to turn it on
Customers without two-factor authentication see a small "Protect your account" window with Set It Up and Not Now after each sign-in (or on every page), and a status box on the My Account dashboard.
Managed by the store
- Customers > Two-Factor Authentication: a grid of the customers who use it (methods, backup codes left, last sign-in with a code, wrong codes, text message consent), with Reset per customer or as a mass action; Reset is also on the customer page and in the customer grid.
- Two-Factor Activity Log of codes, wrong codes, text messages, changes and resets, with automatic cleanup.
- Emails to the customer when a method is turned on or off, new backup codes are created or the store resets it.
- REST and GraphQL tokens cannot be created with email and password alone for these customers (default; can be allowed for mobile apps and headless storefronts).
Luma and Hyvä
The same package works on Luma and Luma-based themes and on Hyvä themes (Hyvä Theme module 1.3 or later, Hyva/default or a child theme), on computers and phones, with no change to Luma. On Hyvä the extension uses hyva_ layout handles, a plain script that reads Hyvä's private content (no RequireJS) and its own plain CSS file, so no Tailwind rebuild is needed; merchants restyle it with --ti-2fa-* CSS custom properties.
Requirements
- Magento Open Source / Adobe Commerce 2.4.9; PHP 8.3, 8.4 or 8.5 with ext-openssl
- Themes: Luma, Luma-based and Hyvä (Hyvä Theme module 1.3+, Hyva/default or a child theme; no Tailwind rebuild needed)
- Optional: endroid/qr-code for the QR code (installed with Magento's admin two-factor authentication); without it customers type the setup key
- Optional: an account with an SMS provider (Twilio, Vonage, Plivo, Sinch, Infobip, Telnyx, Prelude or an HTTP API) for text messages
- Optional: Customer Account Blocks 1.3.0 or later to block accounts after wrong codes
Change Log
Version 1.4.0 – October 30, 2026
New
- Hyvä theme support: the code page after the password (including the check on the last digit), My Account > Two-Factor Authentication, the dashboard box, the mobile number and consent fields of the registration form and the setup suggestion work and look right on Hyvä themes (Hyvä Theme module 1.3+, Hyva/default or a child theme) on computers and phones, without a Tailwind rebuild. hyva_*.xml layout files apply only to Hyvä store views; prompt.js runs as a plain script there and reads the ti-twofactor section from Hyvä's private-content-loaded event; the new css/customer-two-factor-hyva.css is plain CSS restyled with --ti-2fa-* custom properties. Luma-based themes are unchanged.
Improved
- The suggestion script no longer asks for the ti-twofactor section for visitors who are not signed in. Magento reloads the section after a sign-in itself; the script loads it once only for a signed-in customer whose browser never had it (sections cached before the module was installed).
Earlier versions (4)
Version 1.3.0 – October 30, 2026
New
- Remembered devices survive cleared cookies. "Don't ask again on this device" now keeps the device on the server (new table ti_customer_twofactor_device) besides the cookie. When the cookie is gone, the code page first checks the browser ("Checking this device…"): the same browser and system (without version numbers), languages, time zone, graphics card and the way it draws text as when the box was ticked. A match signs the customer in without a code and sets a new cookie (the old cookie secret stops working); otherwise the code is asked as usual. One check per sign-in, at most 10 unknown devices per customer per day. With text messages first, the code is sent only after the check, so a recognised device costs no message.
- Setting Recognise a Remembered Device After Cookies Are Cleared: Same browser (default), Same browser and same network (also the same IPv4 /24 or IPv6 /48 range) or Never (only with the cookie).
- My Account lists the remembered devices ("Chrome on Windows", "This device" for the current one) with when each was remembered, last used and until when.
- The code is checked as soon as its last digit is typed or pasted (6 digits, or 8 characters for a backup code), including codes filled in from a text message by the phone. Verify stays for keyboards and screen readers.
- Activity log event "Device not recognised without its cookie"; a sign-in after cookies were cleared is logged as "Signed in on a remembered device" with the details "Recognised without its cookie".
Improved
- "Don't ask again on this device" is above the code field, so it can be ticked before the code is checked.
- Only keyed hashes are stored for devices: the cookie secret, the browser's characteristics and (in the network mode) the address range are never stored in readable form. The hashes are bound to the customer and the device salt, so Forget All Devices, turning two-factor authentication off and an admin reset end every device, and the nightly cron removes expired devices.
- Cookies set by 1.2.0 keep working until they expire. They are not on the server, so a device remembered by 1.2.0 is asked for the code again once its cookie is cleared; ticking the box then remembers it the new way.
Upgrade
- Run bin/magento setup:upgrade (new table), setup:di:compile in production mode, deploy static content and flush the cache.
Version 1.2.0 – October 28, 2026
New
- Consent to text messages, as US carriers require for A2P 10DLC campaigns (for example Twilio's "Two-Factor authentication" use case): an unticked, required consent box with a disclosure next to the mobile number in My Account, on the Create an Account page (optional) and, for numbers without consent, on the sign-in code page. Without it no code is sent ("Please agree to receive verification codes by text message."). Settings Consent Text (with https://techimprovement.net/, {{terms_url}}, {{privacy_url}}), SMS Terms Page (default sms-terms) and Privacy Policy Page (default privacy-policy-cookie-restriction-mode). The two pages are linked in the consent text only when they are active in the store view.
- Consent records in the new table ti_customer_twofactor_sms_consent: customer, number, page (account, registration, sign_in), website and store view, the exact text, IP address, browser and time. A consent counts on the website where it was given, so a customer account shared between websites agrees again for another brand or sender. A consent is saved only when a message can be sent (provider, hourly and daily limits), at most as many per hour as text messages per hour, and only once per active number. It ends (time and reason) when the customer turns text messages off, the store resets two-factor authentication, another number is used, or the provider reports that the number opted out.
- My Account and the admin grid Customers with Two-Factor Authentication (Text Message Consent, Consent Page, Consent Ended) show the consent of the number in use: the number that receives the codes, else the number being confirmed, else the number given on Create an Account.
- Optional mobile number on the Create an Account page (setting Offer Text Message Codes on Create an Account, default Yes): section "Sign-in protection (optional)" with the same consent box, shown when text messages can be used, the SMS provider is set up and the SMS Terms Page is active in the store view. With consent the number is kept as pending (ti_customer_twofactor.pending_phone) and My Account asks "Confirm your mobile number to turn on text message codes"; nothing is sent and text messages stay off until then. Without consent or with a wrong number only a notice is shown; the registration always goes through. The Social Login popup is not affected.
- Opt-outs: Twilio error 21610 and Telnyx error 40300 (the customer replied STOP) end the consent and tell the customer "This number has opted out of text messages (STOP). Reply START to the sender's number to opt in again, or use your authenticator app or a backup code." The sign-in page stays usable. A number whose consent ended while its code waited cannot be confirmed. Model\Sms\OptedOutException for other providers.
- magento/module-cms dependency (CMS page choice in the settings).
Improved
- Every code, also the sign-in code, is texted only to a number with an active consent on the current website (Model\Sms\Sender::sendCode()). Customers who turned on text messages before 1.2.0 have no consent record (none is made up for them): the sign-in code page shows them the unticked consent box and does not text a code when it opens (it opens with the authenticator app when the customer has it), and My Account asks them to confirm their consent. Nothing is sent until the box is ticked (source sign_in or account). The same applies to numbers whose consent ended (STOP).
- The default Message now names the STOP and HELP keywords, as carriers expect: "{{code}} is your https://techimprovement.net/ verification code. It expires in {{minutes}} minutes. Reply STOP to opt out, HELP for help." A message saved in the settings is kept.
- Turning off text messages also forgets a number that is waiting for confirmation.
- Phone numbers in the SMS provider's error messages are masked in var/log/system.log and in the activity log.
Fixed
- Numbers typed with the leading 1 and the Default Country Calling Code 1 ("1 415 555 0100") are no longer prefixed twice; +1 numbers must have 10 digits after the 1.
- "Wait N seconds before asking for another code" was never applied when setting up text messages in My Account: the time of the previous code was read from the wrong place.
Version 1.1.0 – October 27, 2026
New
- SMS providers Plivo (Auth ID and Token, sender or Powerpack), Sinch (service plan ID and API token, regions us, eu, au, br, ca), Infobip (base URL and API key), Telnyx (API key, sender and/or messaging profile) and Prelude (Verify API). Secrets are stored encrypted and hidden in config dumps.
- Prelude makes the 6-digit code, sends it and checks it (POST /v2/verification and /v2/verification/check), with an optional template and sender ID; the customer's IP address is passed to Prelude's fraud checks. The time a code works and the number of tries still apply. The Message setting is hidden for Prelude.
- Model\Sms\VerificationGatewayInterface for providers that make and check codes themselves, and Model\Sms\JsonApi for JSON requests. Sender::sendCode() now tells whether the provider checks the code, and Sender::checkCode() checks a code typed by the customer.
Fixed
- Behind an outbound HTTP proxy, a refused text message (for example wrong Twilio credentials) was reported as sent: Magento's HTTP client read the proxy's "200 Connection established" as the provider's answer. The proxy's headers are now left out (CURLOPT_SUPPRESS_CONNECT_HEADERS) for every provider.
- A failed test message shows a reason also when the provider answers with an empty or HTML page.
Version 1.0.0 – October 27, 2026
New
- Two-factor authentication for customer accounts: authenticator app (RFC 6238 time-based codes, QR code or setup key, each code works once) and text messages (Twilio, Vonage or any SMS HTTP API, with resend wait, hourly and daily limits, and a test message in the settings).
- Every storefront sign-in through the customer session waits for the code: Sign In page, sign-in popup and checkout sign-in, social sign-in, email confirmation. Not asked on remembered devices, for Login as Customer, store switches and (setting) passkeys. Until the code is right, the visit stays a guest visit.
- Backup codes (10 per set, stored as hashes) with download as a text file, copy and print.
- Remembered devices (30 days by default) with "Forget All Devices".
- Suggestion window after every sign-in (or on every page) until two-factor authentication is set up, and a status box on the My Account dashboard.
- Sign-in ends after 5 wrong codes (setting). Event techimprovement_customer_twofactor_failed, used by Customer Account Blocks 1.3.0+ to block the account after too many wrong codes in a row; the count starts again after a block.
- Customer API tokens (REST, GraphQL) refused for customers with two-factor authentication (setting).
- Emails to the customer about every change.
- Admin: Customers > Two-Factor Authentication (customers grid, activity log, settings), Reset Two-Factor Authentication on the customer page, in the customer grid and in the module's grid, ACL resources.
- Activity log with nightly clean-up (90 days by default).
Reviews
Be the first to review Customer Two-Factor Authentication
Reviews are written by customers with an account. Sign in to tell others how it works on your store.

