@@ -63,7 +63,7 @@ type WebAuthnMetadata = WebAuthnAuthenticationMetadata | WebAuthnRegistrationMet
6363 *
6464 * Conditional mediation (passkey autofill) support:
6565 *
66- * Conditional mediation is **opt-in ** in this SDK via the `authenticate()` parameters .
66+ * Conditional mediation is **server-driven ** in this SDK via WebAuthn metadata (`meta.mediation`) .
6767 *
6868 * ```js
6969 * // Optional: feature-detect conditional UI before attempting
@@ -72,18 +72,29 @@ type WebAuthnMetadata = WebAuthnAuthenticationMetadata | WebAuthnRegistrationMet
7272 * if (supportsConditionalUI) {
7373 * const controller = new AbortController();
7474 *
75- * await WebAuthn.authenticate(step, 'conditional', controller.signal);
75+ * // Optional: provide a signal to cancel an in-flight request
76+ * await WebAuthn.authenticate(step, controller.signal);
7677 * }
7778 * ```
7879 *
7980 * Notes:
80- * - When `mediation` is `'conditional'`, an `AbortSignal` is required.
81+ * - When server-driven mediation is `'conditional'`, an `AbortSignal` will be used.
82+ * If you don't provide one, the SDK will create one.
8183 * - If conditional mediation is requested but not supported by the browser,
8284 * `authenticate()` throws a `NotSupportedError` and sets the hidden WebAuthn outcome to `unsupported`.
8385 * - To enable passkey autofill, add `autocomplete="webauthn"` to your username field:
8486 * `<input type="text" name="username" autocomplete="webauthn" />`
8587 */
8688export abstract class WebAuthn {
89+ private static conditionalAbortController ?: AbortController ;
90+
91+ private static createAbortController ( ) : AbortController {
92+ this . conditionalAbortController ?. abort ( ) ;
93+ const abortController = new AbortController ( ) ;
94+ this . conditionalAbortController = abortController ;
95+ return abortController ;
96+ }
97+
8798 /**
8899 * Determines if the given step is a WebAuthn step.
89100 *
@@ -123,32 +134,29 @@ export abstract class WebAuthn {
123134 * Populates the step with the necessary authentication outcome.
124135 *
125136 * @param step The step that contains WebAuthn authentication data
126- * @param mediation Optional mediation requirement passed through to `navigator.credentials.get()`
127- * @param signal Optional AbortSignal passed through to `navigator.credentials.get()` (required when `mediation` is `'conditional'`)
137+ * @param signal Optional AbortSignal passed through to `navigator.credentials.get()`
128138 * @return The populated step
129139 */
130- public static async authenticate (
131- step : JourneyStep ,
132- mediation ?: CredentialMediationRequirement ,
133- signal ?: AbortSignal ,
134- ) : Promise < JourneyStep > {
140+ public static async authenticate ( step : JourneyStep , signal ?: AbortSignal ) : Promise < JourneyStep > {
135141 const { hiddenCallback, metadataCallback, textOutputCallback } = this . getCallbacks ( step ) ;
136142 if ( hiddenCallback && ( metadataCallback || textOutputCallback ) ) {
137143 let outcome : ReturnType < typeof this . getAuthenticationOutcome > ;
138144 let credential : PublicKeyCredential | null = null ;
145+ let mediation : CredentialMediationRequirement | undefined ;
139146
140147 try {
141148 let publicKey : PublicKeyCredentialRequestOptions ;
142149 if ( metadataCallback ) {
143150 const meta = metadataCallback . getOutputValue ( 'data' ) as WebAuthnAuthenticationMetadata ;
151+ mediation = meta . mediation ;
144152 publicKey = this . createAuthenticationPublicKey ( meta ) ;
145153
146154 if ( mediation === 'conditional' ) {
147- if ( ! signal ) {
148- throw new Error (
149- 'AbortSignal is required for conditional mediation WebAuthn requests' ,
150- ) ;
151- }
155+ // Abort any prior conditional request started by the SDK.
156+ // (If the caller provides their own signal, we still abort the prior SDK-owned one.)
157+ this . conditionalAbortController ?. abort ( ) ;
158+
159+ const abortSignal = signal ?? this . createAbortController ( ) . signal ;
152160
153161 const isConditionalMediationSupported = await this . isConditionalMediationSupported ( ) ;
154162 if ( ! isConditionalMediationSupported ) {
@@ -158,14 +166,21 @@ export abstract class WebAuthn {
158166 e . name = WebAuthnOutcomeType . NotSupportedError ;
159167 throw e ;
160168 }
161- }
162169
163- credential = await this . getAuthenticationCredential (
164- publicKey as PublicKeyCredentialRequestOptions ,
165- mediation ,
166- signal ,
167- ) ;
168- outcome = this . getAuthenticationOutcome ( credential ) ;
170+ credential = await this . getAuthenticationCredential (
171+ publicKey as PublicKeyCredentialRequestOptions ,
172+ mediation ,
173+ abortSignal ,
174+ ) ;
175+ outcome = this . getAuthenticationOutcome ( credential ) ;
176+ } else {
177+ credential = await this . getAuthenticationCredential (
178+ publicKey as PublicKeyCredentialRequestOptions ,
179+ mediation ,
180+ signal ,
181+ ) ;
182+ outcome = this . getAuthenticationOutcome ( credential ) ;
183+ }
169184 } else {
170185 throw new Error (
171186 'No metadata callback found for WebAuthn authentication. Please disable JavaScript in server node.' ,
@@ -511,14 +526,27 @@ export abstract class WebAuthn {
511526 const rpId = parseRelyingPartyId ( relyingPartyId ) ;
512527 const allowCredentialsValue = parseCredentials ( allowCredentials || acceptableCredentials || '' ) ;
513528
514- return {
529+ const options : PublicKeyCredentialRequestOptions = {
515530 challenge : Uint8Array . from ( atob ( challenge ) , ( c ) => c . charCodeAt ( 0 ) ) . buffer ,
516531 timeout,
517- // only add key-value pair if proper value is provided
518- ...( allowCredentialsValue && { allowCredentials : allowCredentialsValue } ) ,
519- ...( userVerification && { userVerification } ) ,
520- ...( rpId && { rpId } ) ,
521532 } ;
533+
534+ // For conditional mediation, allowCredentials can be omitted.
535+ // For standard WebAuthn, it may or may not be present.
536+ // Only add the property if the array is not empty.
537+ if ( allowCredentialsValue && allowCredentialsValue . length > 0 ) {
538+ options . allowCredentials = allowCredentialsValue ;
539+ }
540+
541+ if ( userVerification ) {
542+ options . userVerification = userVerification ;
543+ }
544+
545+ if ( rpId ) {
546+ options . rpId = rpId ;
547+ }
548+
549+ return options ;
522550 }
523551
524552 /**
0 commit comments