This article leads with a decision that got easier today, when the leading open-source passkey module for Magento reached 1.0.0: whether to offer passkeys, and whether to build, buy or install them. Passkeys replace a typed password with the fingerprint, face or PIN the customer already uses to unlock a phone, and they cannot be phished. The answer below comes from installing the leading open-source module on a Magento Open Source 2.4.8-p5 store and running every flow with a real WebAuthn authenticator.
TL;DR — Do not build a Magento 2 passkey module: install the Mage-OS one (mage-os/module-passkey-auth), which runs on plain Magento Open Source 2.4.6+ with PHP 8.2+ and worked end to end on 2.4.8-p5, for customer sign-in and as an admin second factor. Check three things first: it breaks the whole CLI if two-factor authentication (2FA) is disabled (fixed in upstream PR #18), a passkey only works on the exact domain it was created on, and passkey sign-in is limited to 10 requests per 60 seconds per IP address.
Why this matters now
Passkeys moved from "coming soon" to default on the platforms customers use: Apple and Google sync them across a customer’s devices, and password managers store them. Google measured the difference on its own accounts: an average of 14.9 seconds to sign in with a passkey against 30.4 seconds with a password, over about 100 million sign-ins. Vendor studies of shop checkouts report larger numbers, such as double-digit conversion lifts, but they come from companies selling passkey services and should be read that way.
For Magento the timing is concrete: the Mage-OS community module reached 1.0.0 on 28 September 2026, the day this article was published, and it is the first release with a frozen public API.
How a passkey sign-in works
A passkey is a key pair. The private key stays on the customer’s device or in their password manager; the server keeps only the public key, so a database leak exposes nothing that signs anyone in. And because every passkey is bound to one domain, a phishing site on another domain cannot use it.
Any system can follow the same five steps:
1. Challenge: the server sends a random, single-use challenge and its domain.
2. Confirm: the browser asks the authenticator, which checks the fingerprint, face or PIN.
3. Sign: the authenticator signs the challenge with the private key for that domain. At registration it creates the key pair instead and returns the public key.
4. Verify: the server checks the signature against the stored public key, and that challenge, origin and domain are its own.
5. Session: only then does it sign the customer in.
sequenceDiagram
participant U as Customer
participant B as Browser
participant A as Authenticator<br/>(phone, laptop, key)
participant S as Server
rect rgb(240, 245, 255)
Note over U,S: Registration, once per device (customer already signed in)
B->>S: Request registration options
S-->>B: Challenge, user ID, relying-party ID (domain)
B->>A: navigator.credentials.create()
A->>U: Fingerprint, face or PIN
A-->>B: New public key + credential ID
B->>S: Public key + credential ID + origin
S->>S: Check challenge, origin and domain,<br/>store credential ID + public key
end
rect rgb(240, 250, 240)
Note over U,S: Sign-in, every time
B->>S: Request sign-in options
S-->>B: New challenge, relying-party ID
B->>A: navigator.credentials.get()
A->>U: Fingerprint, face or PIN
A-->>B: Challenge signed with the private key
B->>S: Credential ID + signature + origin
S->>S: Look up the public key, verify signature,<br/>challenge, origin and domain
S-->>B: Session starts
end
The baseline: what Magento 2 does without a module
Magento Open Source has no passkey login. Customers sign in with email and password. Admins sign in with username and password, followed by mandatory two-factor authentication from Magento_TwoFactorAuth, which ships Google Authenticator, Duo, Authy and a "U2F key" provider. That last one already uses WebAuthn, the standard behind passkeys, but only as a second factor after the password, and only for admins.
Since mid-2026 several modules fill the gap:
| Module | Scope | State (September 2026) |
|---|---|---|
| mage-os/module-passkey-auth | Customers (Luma and Hyvä), REST and GraphQL, admin 2FA provider | 1.0.0, OSL-3.0, community-maintained |
| falconmedia/magento2-admin-passkey | Admin passkey login with 2FA fallback | 1.0.1, PHP 8.3+, not tested here |
| CustomGento passkey module | Passwordless admin login | Commercial, not tested here |
| MageMate/magento-admin-passkey | Admin passkey login | Single commit |
| dmlab / magedevgroup / 5mehulhelp5 customer-passkey packages | Customers | 0.x versions |
| MojoAuth | Customers, through a hosted OIDC login page | Third-party service in every login |
The tradeoff: which way to add Magento 2 passkeys
Install the Mage-OS module
The community option: WebAuthn ceremonies run inside Magento with web-auth/webauthn-lib 5, no external identity provider, and the store is the relying party.
- Strengths: customer passkeys on the login page, in My Account and at checkout; an admin 2FA provider; an admin grid to revoke passkeys; REST and GraphQL for headless storefronts; Luma and Hyvä templates; notification emails; 340 unit tests.
- Costs: a new dependency tree (16 packages), three traps covered below, and the usual community-module risk: the maintainers are volunteers.
Use an admin-only passkey module
FalconMedia’s and CustomGento’s modules replace the admin password with a passkey instead of adding a second factor. That is the bigger security step for the admin, where a phished password does the most damage, but it does nothing for customers.
- Strengths: passwordless admin sign-in; smaller scope.
- Costs: not tested for this article; one more module owning the most sensitive login of the store.
Use a hosted passkey service
MojoAuth and similar services redirect the customer to a hosted login page over OpenID Connect and hand back an identity.
- Strengths: passkey, magic link and one-time-code fallbacks from one vendor; no WebAuthn code in the store.
- Costs: a third party in every customer login, a monthly bill, and a dependency on their uptime at checkout.
For a store that wants customer passkeys without a new service in the login path, the Mage-OS module is the sensible default, and building a seventh module would compete with it on its release day.
A working example: installing and testing it on 2.4.8-p5
Here is a working example on Magento Open Source 2.4.8-p5, PHP 8.4, production mode, Luma:
composer require mage-os/module-passkey-auth
bin/magento module:enable MageOS_PasskeyAuth
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento config:set customer/passkey/enabled 1
bin/magento cache:flush
Composer resolved it against the store’s locked packages without changing a single existing one; it added 16, from web-auth/webauthn-lib 5.3 to Symfony serializer components. The module only requires magento/* package names, so Mage-OS itself is not a prerequisite, and its stated support is Magento Open Source or Mage-OS 2.4.6+ with PHP 8.2+.
Customer flow. A customer signs in with the password once, gets an enrollment banner, and adds a passkey under My Account › Passkeys after naming it. On the next visit, "Sign in with Passkey" signs them in without typing an email: the browser offers the stored passkey, the fingerprint or face confirms it, and the account page opens.
Admin flow. Add Passkey to Stores > Configuration > Security > 2FA > Providers to use. The next admin sign-in asks for the password, then sends Magento’s 2FA setup email; its link opens the passkey registration. From then on, each admin sign-in is password first, then "Authenticate with Passkey" instead of a code from an app.
Both flows were run with Chrome’s virtual authenticator, which creates and uses real WebAuthn credentials without a phone. That is also the way to test passkeys in CI:
const cdp = await context.newCDPSession(page);
await cdp.send('WebAuthn.enable');
const { authenticatorId } = await cdp.send('WebAuthn.addVirtualAuthenticator', {
options: {
protocol: 'ctap2', transport: 'internal',
hasResidentKey: true, hasUserVerification: true,
isUserVerified: true, automaticPresenceSimulation: true,
},
});
// ... register and sign in through the storefront, then inspect the credential:
const { credentials } = await cdp.send('WebAuthn.getCredentials', { authenticatorId });
Three traps to check before going live
1. With 2FA disabled, every bin/magento command fails
Many stores disable Magento_TwoFactorAuth on development and staging, and some replace it in production. On such a store, enabling the module breaks the command line entirely, setup:upgrade and cache:flush included:
Cannot instantiate interface Magento\TwoFactorAuth\Api\UserConfigManagerInterface
bin/magento builds every console command on start, and the module’s security:tfa:passkey:reset-all command needs a 2FA service that has no implementation while 2FA is off. Magento does not stop it: module:disable Magento_TwoFactorAuth is refused while the passkey module is on, but module:enable MageOS_PasskeyAuth succeeds when 2FA is already off. The fix is a lazy proxy for that one constructor argument; it is submitted upstream as mage-os-lab/module-passkey-auth#18. Until it is released, add it to your own etc/di.xml:
<type name="MageOS\PasskeyAuth\Console\Command\ResetPasskeyTfaCommand">
<arguments>
<argument name="userConfigManager" xsi:type="object">Magento\TwoFactorAuth\Api\UserConfigManagerInterface\Proxy</argument>
</arguments>
</type>
2. A passkey belongs to one exact domain
WebAuthn binds every passkey to a relying-party ID, and the module uses the host of the store’s base URL (for admins, the host of the admin URL). In the test the credential was created for exactly magento2-sandbox.ddev.site. What that means for a real store:
www.shop.comandshop.comare different domains for a passkey: serve the store on one and redirect the other before customers create passkeys.- A multi-website store on
shop.deandshop.atwith shared customer accounts needs a passkey per domain. WebAuthn Related Origin Requests, which would let one passkey cover several domains, are not used by the module. - Moving the store to a new domain invalidates every customer passkey. Customers fall back to the password and create new ones.
- A headless frontend on a different domain from the Magento base URL cannot use it.
3. The rate limit is per IP address, not per customer
To protect the endpoints, the module allows 10 passkey option requests per 60 seconds for the same email and IP. Passkey sign-in without an email, the one-tap button and the autofill prompt, uses the IP alone. And the login page and the checkout request those options on every view, whether the visitor uses passkeys or not.
Measured with 12 separate visitors from one IP within 31 seconds:
| Visitors | Passkey options |
|---|---|
| 1–10 | Delivered (HTTP 200) |
| 11 and 12 | Refused: "Too many passkey requests. Please try again later." |
Customers behind one office network or a mobile carrier’s shared addresses therefore share one budget. On a store behind a load balancer, Varnish or a CDN that does not hand the real client IP to Magento, every visitor shares it: the whole store gets 10 login-page and checkout views per minute before passkey sign-in stops working. Check that Magento logs real client IPs before enabling the module.
What to skip
- Building your own passkey module. The Mage-OS one covers customers, admin 2FA, APIs and both themes; improve it instead.
- Treating passkeys as a password replacement. The module adds passkeys next to the password; the password keeps working, and "Forgot your password?" stays the recovery path for a lost device. A phished password still signs in. The phishing protection applies to the customers who use their passkey.
- Store-view configuration. The settings are meant for Default and Website scope, and that is enough: enable passkeys per website, or leave customer passkeys off and use only the admin provider.
- Quoting vendor conversion numbers in the business case. Use your own sign-in success rate and time to sign in, before and after.
Verification
Four checks after installing:
1. CLI with your 2FA setup: run bin/magento list with the store’s real Magento_TwoFactorAuth status. If it fails with the error above, apply the proxy.
2. Domain: open the login page on the canonical domain only, register a passkey, and confirm in the browser’s passkey manager that it is stored for that exact host.
3. Client IP: place a test order and check remote_ip on the order. If it shows the load balancer’s or Varnish’s address, fix the real-IP configuration before enabling passkeys.
4. Rate limit: open the login page 11 times within a minute from one network; the 11th view should refuse passkey options. That is expected, but it tells you how quickly a shared office network reaches the limit.
The admin side is verified by signing in as an admin: password, then "Authenticate with Passkey", then the dashboard.
Related reading
- mage-os-lab/module-passkey-auth: the module, its manual and the security notes.
- Making authentication faster than ever: passkeys vs. passwords: Google’s measurements behind the sign-in times.
- Related Origin Requests on passkeys.dev: how one passkey can cover several domains, the piece a multi-domain store would need.
- Magento 2 Security Hardening: where passkeys fit among the other layers of admin and customer security.