Why Shop Moved to Passkeys

Shop Pay and the Shop app have long relied on email or SMS verification codes for authentication. That approach works, but its security ceiling is set by the email provider or phone carrier in question—if an attacker compromises that inbox or SIM, the Shop account is exposed. Passkeys, which use public-key cryptography instead of shared secrets, remove that dependency entirely.

Starting in December 2022, Shopify began rolling out passkeys across Shop's web and native app authentication flows. The credentials are supported through Google Password Manager on Android and Chrome, and through iCloud Keychain on iOS and Safari. The model is simple: during registration, a private key is generated and stored locally on the user's device, while the server only ever sees a public key. Because there is no shared secret transmitted or stored, credential leaks and phishing attacks lose their primary vector. Passkeys work with platform authenticators, which are biometric components built into a device; roaming authenticators such as YubiKeys; or other software methods that meet high security requirements.

Enabling Passkeys Beyond the Shop Domain

Shop Pay runs on Ruby on Rails, and the team built passkey support on the community-maintained webauthn-ruby gem. The goal wasn't limited to Shop's own properties: passkeys issued on the Shop domain should work on any Shopify merchant storefront where a customer logs in with Shop Pay. That meant integrating passkeys into the login component and supporting their use inside an iframe, which is allowed under the WebAuthn Level 2 specification.

The Shop Pay passkeys login page
When users are given the option to login, they can tap the Passkeys button and their device will allow them to select from a list of passkeys previously registered on the device.

The Registration Flow

Passkeys are being introduced as an additional authentication method for existing Shop users, who first sign up and verify via email or SMS before they can enroll a passkey. The registration sequence:

  1. The user selects the option to enroll a new passkey.
  2. The backend calls WebAuthn::Credential.options_for_create, configured so only one passkey can be registered per device.
  3. That call generates a challenge, which the server persists for the duration of verification and also sends to the client.
  4. The browser prompts for biometric authentication, which is used to generate the private/public keypair.
  5. The server receives the public key and verifies that it corresponds to the stored challenge and originates from an allowed domain.
  6. WebAuthn::RelyingParty.verify_registration is called with the browser's credentials and the challenge.
  7. The verified credential is stored in the database, associated with the id returned by the verification call.
  8. The user sees confirmation that the passkey was added.
A diagram of the passkeys registration flow
Passkeys registration flow

The Login Flow

Authentication with an existing passkey follows nearly the same path, diverging only at the final steps:

  1. The server identifies the stored credential using the id originally returned from WebAuthn::RelyingParty.verify_registration.
  2. WebAuthn::RelyingParty.verify_authentication authenticates the user and updates the sign count.

Implementation Lessons

Shop's engineering team surfaced three practical takeaways from the rollout:

  • Passkeys tied to a domain work across all its subdomains. If a passkey should only authenticate against specific subdomains, that list must be explicitly whitelisted.
  • Set authenticator_selection: "platform" to request platform authenticators (Touch ID, fingerprint readers) rather than security keys, and set require_resident_key to true to make the credential discoverable—that is, a passkey.
  • Persist the challenge to the database for the duration of verification, confirm it when the public key arrives, and delete it once matched to block replay attacks.

Conditional UI

One area of active experimentation is Conditional UI, a WebAuthn feature in which the browser or operating system surfaces available passkeys as a user approaches a login form, removing the need to tap a separate passkey button. For Shop Pay, which appears embedded on merchant storefronts, Conditional UI could be the clearest signal that a Shop passkey is usable on the page. Browser and OS vendors are still refining both the passkey-available and passkey-unavailable states, and the "no passkeys" experience on desktop can be jarring for first-time users. Shopify is continuing to test Conditional UI as those implementations mature.

A diagram of the passkeys login flow
Passkeys login flow

What Needs to Happen Next

Passkeys offer a distinctly better security posture than passwords: credentials live on a device and can't be phished or bulk-leaked from a server. But passwords have one advantage passkeys still lack—universality. Any device, any browser, any operating system: passwords just work. For passkeys to reach that level of adoption, providers need to improve cross-device syncing and interoperability so a passkey created on a phone is usable on a desktop, regardless of OS or browser vendor.

Shop has enabled passkeys in Shop Pay, the Sign in with Shop web flow, and the Shop app. The team plans to extend support to every surface where customers interact with Shop, and to enable Conditional UI once it is polished. As device and browser support matures, broad passkey availability becomes a meaningful security upgrade for the millions of users making purchases through Shop Pay.