Summary
Implement built-in passwordless authentication in Ultimate Multisite using an email-first flow with native WebAuthn/passkey support and email OTP fallback.
This should live in Ultimate Multisite itself. Do not depend on the secure-passkeys WordPress plugin at runtime. Its WebAuthn implementation can be used as a reference for challenge generation, browser APIs, credential persistence, and assertion/attestation verification, but Ultimate Multisite should ship only the focused pieces needed for this product flow.
Desired user flow
Login
- User sees a login form with email only.
- User submits their email.
- Ultimate Multisite checks whether the matching user has at least one registered passkey.
- If a passkey exists:
- stay on the same login page/form;
- prompt the browser passkey ceremony using
navigator.credentials.get();
- complete login on successful WebAuthn assertion.
- If no passkey exists:
- send a short-lived email OTP/code;
- stay on the same login page/form;
- prompt for the code;
- complete login on successful OTP verification.
- After the first successful OTP login, prompt the user to create a passkey on the login page using
navigator.credentials.create().
- Passkey registration must not require visiting the WordPress user profile screen.
Surfaces that must support the flow
- Default
wp-login.php.
- Ultimate Multisite custom login page when
enable_custom_login_page is enabled.
- Ultimate Multisite login block/shortcode/element rendered by
WP_Ultimo\UI\Login_Form_Element.
- Checkout inline login prompt shown when an existing email/username is entered during checkout/signup.
Current relevant code
Use these files as the primary integration points:
inc/class-wp-ultimo.php
- Registers singleton services including
WP_Ultimo\UI\Login_Form_Element and SSO classes.
- Add the new passwordless/passkey service here once implemented.
inc/ui/class-login-form-element.php
- Current login form element builds username/password fields around lines ~777-845.
- Current form posts to
site_url('wp-login.php', 'login_post') around lines ~876-889.
- Existing reset/lost-password behaviour must keep working unless intentionally replaced by the new passwordless flow.
views/dashboard-widgets/login-form.php
- Renders the login form element wrapper and title.
views/checkout/partials/inline-login-prompt.php
- Currently asks for a password for existing users during checkout/signup.
- Replace/extend this with the email-first passkey/OTP flow.
inc/checkout/class-checkout-pages.php
- Owns custom login page routing and
wp-login.php URL replacement.
- Preserve custom-login-page URL behaviour, especially subsite-domain password/reset link handling.
inc/sso/class-sso.php and inc/sso/*
- Preserve existing multisite SSO behaviour and redirect-loop protections.
- Ensure successful passkey/OTP login uses the same redirect/SSO semantics as current password login.
- Existing tests to update/extend:
tests/e2e/cypress/integration/login.spec.js
tests/e2e/cypress/support/commands/login.js
tests/WP_Ultimo/Checkout/Checkout_Pages_Test.php
tests/WP_Ultimo/SSO/SSO_Test.php
Suggested implementation shape
Create a focused authentication subsystem, for example:
inc/auth/class-passwordless-auth.php or inc/managers/class-passwordless-auth-manager.php
inc/auth/class-passkey-service.php
inc/auth/class-email-otp-service.php
inc/auth/class-webauthn-challenge-store.php
inc/auth/class-passkey-credential-store.php
The exact structure is flexible, but keep the feature cohesive and avoid mixing WebAuthn protocol logic directly into UI templates.
Data storage
Add minimal storage for:
- WebAuthn credentials:
- user ID
- credential ID / raw ID
- public key
- sign count/counter
- AAGUID if available
- created/updated/last-used timestamps
- active flag
- WebAuthn challenges:
- challenge value
- type: registration or authentication
- optional user ID
- expiration timestamp
- used timestamp/status
- Email OTP attempts:
- store hashed OTP only, never plaintext;
- user ID/email association;
- expiration timestamp;
- attempt count;
- consumed timestamp/status.
Use Ultimate Multisite's existing database/table conventions where practical ({$wpdb->prefix}wu_*, installer/migrator patterns, and tests for table creation). Avoid adding large admin/activity-log surfaces in this first pass.
WebAuthn requirements
Implement passkey registration and authentication directly in Ultimate Multisite:
- Generate registration options for a specific authenticated/OTP-verified user.
- Verify registration attestation response and store credential data.
- Generate authentication options scoped to the entered email/user when possible.
- Verify assertion response, credential ID, challenge, origin/RP ID, signature, and sign counter.
- Use HTTPS-origin-safe logic and multisite/domain-mapping-safe RP ID selection.
- Use WordPress nonces on AJAX/REST endpoints.
- Use
wp_set_auth_cookie(), wp_set_current_user(), and standard login hooks so SSO, redirects, and audit hooks continue to work.
Reference only: the WordPress.org secure-passkeys plugin implements a similar split with frontend AJAX actions for login options/login/register options/register passkey and JS using navigator.credentials.get() / navigator.credentials.create(). Do not copy its admin overview, activity log, profile-page UX, or settings pages unless a small piece is actually needed.
Email OTP requirements
- Code should be short-lived, single-use, and rate-limited.
- Store only a hash of the code.
- Apply rate limits by user/email and IP to prevent brute force and mail flooding.
- Return generic responses where practical to reduce email enumeration.
- Email should use Ultimate Multisite/WordPress mail patterns and be translatable with text domain
ultimate-multisite.
- On successful OTP login, immediately show/passkey-enrol prompt on the same page.
UI/UX requirements
The login UI should be a progressive state machine on one page:
- Email entry.
- Passkey prompt if available, otherwise OTP prompt.
- OTP verification.
- First-OTP-login passkey enrolment prompt.
- Completion/redirect.
Do not send users to /wp-admin/profile.php to register a passkey.
Keep copy using the product name “Ultimate Multisite” where product naming is needed. Preserve the WP_Ultimo namespace and wu_ hooks/prefixes.
Backward compatibility and migration notes
- Existing username/password login may remain as an emergency/admin fallback if needed, but the customer-facing Ultimate Multisite login surfaces should default to email-first passwordless auth.
- Existing custom login page behaviour and
wp-login.php URL rewrite tests must remain valid.
- Existing SSO redirect-loop guards must remain valid.
- Existing reset-password flows should not break unexpectedly; if reset password becomes secondary, keep compatibility for existing links and tests.
- The old two-factor assumptions should be removed from any Ultimate Multisite/passwordless integration work; this feature must not require the WordPress Two-Factor plugin.
Security considerations
- Auth feature: treat as security-sensitive.
- Never log OTPs, raw WebAuthn challenges, credential private data, or email codes.
- Validate all AJAX/REST requests with nonces and capabilities/context checks.
- Sanitize all request values with WordPress helpers.
- Escape all UI output.
- Avoid account enumeration where possible.
- Add brute-force and resend throttling for OTP and passkey challenge attempts.
- Ensure passkey challenges are single-use and expire quickly.
- Ensure authentication cookies are set only after verified passkey assertion or OTP.
Acceptance criteria
Suggested tests / verification
PHP/unit/integration
Add or update focused tests for:
- passkey credential/challenge storage and expiry;
- OTP generation, hashing, expiry, consumption, and attempt limits;
- login redirect behaviour after passkey and OTP success;
- custom login page compatibility in
Checkout_Pages;
- SSO compatibility where login originates from a subsite/domain-mapped site.
Likely commands:
vendor/bin/phpunit --filter Login_Form_Element
vendor/bin/phpunit --filter Checkout_Pages_Test
vendor/bin/phpunit --filter SSO_Test
vendor/bin/phpunit --filter Passwordless
vendor/bin/phpcs inc/auth tests/WP_Ultimo
vendor/bin/phpstan analyse
Use exact filters/classes created by the implementation; do not run non-existent filters.
E2E
Update Cypress login coverage:
tests/e2e/cypress/integration/login.spec.js
tests/e2e/cypress/support/commands/login.js
Scenarios:
- email-only login form appears;
- OTP fallback flow can be completed using a captured/test email code;
- passkey-supported browser path is wired and gracefully skipped/mocked where WebAuthn cannot run in CI;
- checkout inline login no longer requires a password field.
Manual local verification
- Activate Ultimate Multisite in the local multisite dev environment.
- Enable custom login page.
- Verify
/wp-login.php, the configured custom login page, and checkout inline login all show the email-first flow.
- Create/log in a user without passkey via OTP and register a passkey on the login page.
- Log out and confirm the same email now routes to passkey login.
- Confirm failed/expired OTP and cancelled passkey ceremonies show safe errors without logging the user in.
Notes for implementer
- Keep implementation PHP 7.4 compatible.
- Follow WordPress Coding Standards used by this repo.
- Public/base class method signatures should not add return type declarations where addon compatibility could be affected.
- Use relative repo paths only in PR descriptions/comments.
- Include migration/schema changes and rollback-safe tests in the PR.
aidevops.sh v3.20.41 plugin for OpenCode v1.16.2 with gpt-5.5
Summary
Implement built-in passwordless authentication in Ultimate Multisite using an email-first flow with native WebAuthn/passkey support and email OTP fallback.
This should live in Ultimate Multisite itself. Do not depend on the
secure-passkeysWordPress plugin at runtime. Its WebAuthn implementation can be used as a reference for challenge generation, browser APIs, credential persistence, and assertion/attestation verification, but Ultimate Multisite should ship only the focused pieces needed for this product flow.Desired user flow
Login
navigator.credentials.get();navigator.credentials.create().Surfaces that must support the flow
wp-login.php.enable_custom_login_pageis enabled.WP_Ultimo\UI\Login_Form_Element.Current relevant code
Use these files as the primary integration points:
inc/class-wp-ultimo.phpWP_Ultimo\UI\Login_Form_Elementand SSO classes.inc/ui/class-login-form-element.phpsite_url('wp-login.php', 'login_post')around lines ~876-889.views/dashboard-widgets/login-form.phpviews/checkout/partials/inline-login-prompt.phpinc/checkout/class-checkout-pages.phpwp-login.phpURL replacement.inc/sso/class-sso.phpandinc/sso/*tests/e2e/cypress/integration/login.spec.jstests/e2e/cypress/support/commands/login.jstests/WP_Ultimo/Checkout/Checkout_Pages_Test.phptests/WP_Ultimo/SSO/SSO_Test.phpSuggested implementation shape
Create a focused authentication subsystem, for example:
inc/auth/class-passwordless-auth.phporinc/managers/class-passwordless-auth-manager.phpinc/auth/class-passkey-service.phpinc/auth/class-email-otp-service.phpinc/auth/class-webauthn-challenge-store.phpinc/auth/class-passkey-credential-store.phpThe exact structure is flexible, but keep the feature cohesive and avoid mixing WebAuthn protocol logic directly into UI templates.
Data storage
Add minimal storage for:
Use Ultimate Multisite's existing database/table conventions where practical (
{$wpdb->prefix}wu_*, installer/migrator patterns, and tests for table creation). Avoid adding large admin/activity-log surfaces in this first pass.WebAuthn requirements
Implement passkey registration and authentication directly in Ultimate Multisite:
wp_set_auth_cookie(),wp_set_current_user(), and standard login hooks so SSO, redirects, and audit hooks continue to work.Reference only: the WordPress.org
secure-passkeysplugin implements a similar split with frontend AJAX actions for login options/login/register options/register passkey and JS usingnavigator.credentials.get()/navigator.credentials.create(). Do not copy its admin overview, activity log, profile-page UX, or settings pages unless a small piece is actually needed.Email OTP requirements
ultimate-multisite.UI/UX requirements
The login UI should be a progressive state machine on one page:
Do not send users to
/wp-admin/profile.phpto register a passkey.Keep copy using the product name “Ultimate Multisite” where product naming is needed. Preserve the
WP_Ultimonamespace andwu_hooks/prefixes.Backward compatibility and migration notes
wp-login.phpURL rewrite tests must remain valid.Security considerations
Acceptance criteria
secure-passkeysplugin.wp-login.phpsupports the new flow.WP_Ultimo\UI\Login_Form_Elementlogin block/shortcode supports the new flow.Suggested tests / verification
PHP/unit/integration
Add or update focused tests for:
Checkout_Pages;Likely commands:
Use exact filters/classes created by the implementation; do not run non-existent filters.
E2E
Update Cypress login coverage:
tests/e2e/cypress/integration/login.spec.jstests/e2e/cypress/support/commands/login.jsScenarios:
Manual local verification
/wp-login.php, the configured custom login page, and checkout inline login all show the email-first flow.Notes for implementer
aidevops.sh v3.20.41 plugin for OpenCode v1.16.2 with gpt-5.5