@@ -60,6 +60,24 @@ type WebAuthnTextOutput = WebAuthnTextOutputRegistration;
6060 * await FRWebAuthn.authenticate(step);
6161 * }
6262 * ```
63+ *
64+ * Conditional UI (Autofill) Support:
65+ *
66+ * ```js
67+ * // Check if browser supports conditional UI
68+ * const supportsConditionalUI = await FRWebAuthn.isConditionalUISupported();
69+ *
70+ * if (supportsConditionalUI) {
71+ * // The authenticate() method automatically handles conditional UI
72+ * // when the server indicates support via conditionalWebAuthn: true
73+ * // in the metadata. No additional code changes needed.
74+ * await FRWebAuthn.authenticate(step);
75+ *
76+ * // For conditional UI to work in the browser, add autocomplete="webauthn"
77+ * // to your username input field:
78+ * // <input type="text" name="username" autocomplete="webauthn" />
79+ * }
80+ * ```
6381 */
6482abstract class FRWebAuthn {
6583 /**
@@ -94,8 +112,27 @@ abstract class FRWebAuthn {
94112 }
95113 }
96114
115+ /**
116+ * Checks if the browser supports conditional UI (autofill) for WebAuthn.
117+ *
118+ * @return Promise<boolean> indicating if conditional mediation is available
119+ */
120+ public static async isConditionalUISupported ( ) : Promise < boolean > {
121+ if ( ! window . PublicKeyCredential ) {
122+ return false ;
123+ }
124+
125+ // Check if the browser supports conditional mediation
126+ if ( typeof PublicKeyCredential . isConditionalMediationAvailable === 'function' ) {
127+ return await PublicKeyCredential . isConditionalMediationAvailable ( ) ;
128+ }
129+
130+ return false ;
131+ }
132+
97133 /**
98134 * Populates the step with the necessary authentication outcome.
135+ * Automatically handles conditional UI if indicated by the server metadata.
99136 *
100137 * @param step The step that contains WebAuthn authentication data
101138 * @return The populated step
@@ -108,19 +145,27 @@ abstract class FRWebAuthn {
108145
109146 try {
110147 let publicKey : PublicKeyCredentialRequestOptions ;
148+ let useConditionalUI = false ;
149+
111150 if ( metadataCallback ) {
112151 const meta = metadataCallback . getOutputValue ( 'data' ) as WebAuthnAuthenticationMetadata ;
152+
153+ // Check if server indicates conditional UI should be used
154+ useConditionalUI = meta . conditionalWebAuthn === true ;
155+
113156 publicKey = this . createAuthenticationPublicKey ( meta ) ;
114157
115158 credential = await this . getAuthenticationCredential (
116159 publicKey as PublicKeyCredentialRequestOptions ,
160+ useConditionalUI ,
117161 ) ;
118162 outcome = this . getAuthenticationOutcome ( credential ) ;
119163 } else if ( textOutputCallback ) {
120164 publicKey = parseWebAuthnAuthenticateText ( textOutputCallback . getMessage ( ) ) ;
121165
122166 credential = await this . getAuthenticationCredential (
123167 publicKey as PublicKeyCredentialRequestOptions ,
168+ false , // Script-based callbacks don't support conditional UI
124169 ) ;
125170 outcome = this . getAuthenticationOutcome ( credential ) ;
126171 } else {
@@ -300,18 +345,36 @@ abstract class FRWebAuthn {
300345 * Retrieves the credential from the browser Web Authentication API.
301346 *
302347 * @param options The public key options associated with the request
348+ * @param useConditionalUI Whether to use conditional UI (autofill)
303349 * @return The credential
304350 */
305351 public static async getAuthenticationCredential (
306352 options : PublicKeyCredentialRequestOptions ,
353+ useConditionalUI = false ,
307354 ) : Promise < PublicKeyCredential | null > {
308- // Feature check before we attempt registering a device
355+ // Feature check before we attempt authenticating
309356 if ( ! window . PublicKeyCredential ) {
310357 const e = new Error ( 'PublicKeyCredential not supported by this browser' ) ;
311358 e . name = WebAuthnOutcomeType . NotSupportedError ;
312359 throw e ;
313360 }
314- const credential = await navigator . credentials . get ( { publicKey : options } ) ;
361+ // Build the credential request options
362+ const credentialRequestOptions : CredentialRequestOptions = {
363+ publicKey : options ,
364+ } ;
365+
366+ // Add conditional mediation if requested and supported
367+ if ( useConditionalUI ) {
368+ const isConditionalSupported = await this . isConditionalUISupported ( ) ;
369+ if ( isConditionalSupported ) {
370+ credentialRequestOptions . mediation = 'conditional' as CredentialMediationRequirement ;
371+ } else {
372+ // eslint-disable-next-line no-console
373+ console . warn ( 'Conditional UI was requested, but is not supported by this browser.' ) ;
374+ }
375+ }
376+
377+ const credential = await navigator . credentials . get ( credentialRequestOptions ) ;
315378 return credential as PublicKeyCredential ;
316379 }
317380
@@ -433,22 +496,51 @@ abstract class FRWebAuthn {
433496 const {
434497 acceptableCredentials,
435498 allowCredentials,
499+ _allowCredentials,
436500 challenge,
437501 relyingPartyId,
502+ _relyingPartyId,
438503 timeout,
439504 userVerification,
505+ extensions,
440506 } = metadata ;
441- const rpId = parseRelyingPartyId ( relyingPartyId ) ;
442- const allowCredentialsValue = parseCredentials ( allowCredentials || acceptableCredentials || '' ) ;
443507
444- return {
508+ // Use the structured _allowCredentials if available, otherwise parse the string format
509+ let allowCredentialsValue : PublicKeyCredentialDescriptor [ ] | undefined ;
510+ if ( _allowCredentials && Array . isArray ( _allowCredentials ) ) {
511+ allowCredentialsValue = _allowCredentials ;
512+ } else {
513+ allowCredentialsValue = parseCredentials ( allowCredentials || acceptableCredentials || '' ) ;
514+ }
515+
516+ // Use _relyingPartyId if available, otherwise parse the old format
517+ const rpId = _relyingPartyId || parseRelyingPartyId ( relyingPartyId ) ;
518+
519+ const options : PublicKeyCredentialRequestOptions = {
445520 challenge : Uint8Array . from ( atob ( challenge ) , ( c ) => c . charCodeAt ( 0 ) ) . buffer ,
446521 timeout,
447- // only add key-value pair if proper value is provided
448- ...( allowCredentialsValue && { allowCredentials : allowCredentialsValue } ) ,
449- ...( userVerification && { userVerification } ) ,
450- ...( rpId && { rpId } ) ,
451522 } ;
523+ // For conditional UI, allowCredentials can be omitted.
524+ // For standard WebAuthn, it may or may not be present.
525+ // Only add the property if the array is not empty.
526+ if ( allowCredentialsValue && allowCredentialsValue . length > 0 ) {
527+ options . allowCredentials = allowCredentialsValue ;
528+ }
529+
530+ // Add optional properties only if they have values
531+ if ( userVerification ) {
532+ options . userVerification = userVerification ;
533+ }
534+
535+ if ( rpId ) {
536+ options . rpId = rpId ;
537+ }
538+
539+ if ( extensions && Object . keys ( extensions ) . length > 0 ) {
540+ options . extensions = extensions ;
541+ }
542+
543+ return options ;
452544 }
453545
454546 /**
0 commit comments