@@ -182,6 +182,40 @@ export const AuthorizerBasicAuthLogin: FC<{
182182 }
183183 } , [ formData . password ] ) ;
184184
185+ // Passkey autofill (WebAuthn conditional mediation): when the browser and the
186+ // user's device support it, discoverable passkeys are offered inline in the
187+ // email/username field's autofill dropdown. Best-effort and silent - the
188+ // explicit "Sign in with a passkey" button remains the primary path, and any
189+ // unsupported/cancelled/aborted ceremony is ignored. Started once on mount
190+ // and aborted on unmount.
191+ useEffect ( ( ) => {
192+ let active = true ;
193+ authorizerRef
194+ . loginWithPasskeyAutofill ( )
195+ . then ( ( { data, errors } ) => {
196+ if ( ! active || ( errors && errors . length ) || ! data ) {
197+ return ;
198+ }
199+ setAuthData ( {
200+ user : data . user || null ,
201+ token : data ,
202+ config,
203+ loading : false ,
204+ } ) ;
205+ if ( onLogin ) {
206+ onLogin ( data ) ;
207+ }
208+ } )
209+ . catch ( ( ) => {
210+ // Autofill is best-effort; ignore unsupported/cancelled ceremonies.
211+ } ) ;
212+ return ( ) => {
213+ active = false ;
214+ authorizerRef . cancelPasskeyAutofill ( ) ;
215+ } ;
216+ // eslint-disable-next-line react-hooks/exhaustive-deps
217+ } , [ ] ) ;
218+
185219 if ( totpData . is_screen_visible ) {
186220 return (
187221 < AuthorizerTOTPScanner
@@ -238,6 +272,10 @@ export const AuthorizerBasicAuthLogin: FC<{
238272 } `}
239273 placeholder = { getEmailPhonePlaceholder ( config ) }
240274 type = "text"
275+ // Enables WebAuthn passkey autofill: browsers offer discoverable
276+ // passkeys inline in this field's autofill dropdown. Paired with
277+ // the loginWithPasskeyAutofill() ceremony started on mount below.
278+ autoComplete = "username webauthn"
241279 value = { formData . email_or_phone_number || '' }
242280 onChange = { e =>
243281 onInputChange ( 'email_or_phone_number' , e . target . value )
0 commit comments