@@ -38,6 +38,7 @@ import type {
3838 SignInFutureEmailLinkSendParams ,
3939 SignInFutureFinalizeParams ,
4040 SignInFutureMFAPhoneCodeVerifyParams ,
41+ SignInFuturePasskeyParams ,
4142 SignInFuturePasswordParams ,
4243 SignInFuturePhoneCodeSendParams ,
4344 SignInFuturePhoneCodeVerifyParams ,
@@ -979,6 +980,77 @@ class SignInFuture implements SignInFutureResource {
979980 } ) ;
980981 }
981982
983+ async passkey ( params ?: SignInFuturePasskeyParams ) : Promise < { error : unknown } > {
984+ const { flow } = params || { } ;
985+
986+ /**
987+ * The UI should always prevent from this method being called if WebAuthn is not supported.
988+ * As a precaution we need to check if WebAuthn is supported.
989+ */
990+
991+ const isWebAuthnSupported = SignIn . clerk . __internal_isWebAuthnSupported || isWebAuthnSupportedOnWindow ;
992+ const webAuthnGetCredential = SignIn . clerk . __internal_getPublicCredentials || webAuthnGetCredentialOnWindow ;
993+ const isWebAuthnAutofillSupported =
994+ SignIn . clerk . __internal_isWebAuthnAutofillSupported || isWebAuthnAutofillSupportedOnWindow ;
995+
996+ if ( ! isWebAuthnSupported ( ) ) {
997+ throw new ClerkWebAuthnError ( 'Passkeys are not supported' , {
998+ code : 'passkey_not_supported' ,
999+ } ) ;
1000+ }
1001+
1002+ return runAsyncResourceTask ( this . resource , async ( ) => {
1003+ if ( flow === 'autofill' || flow === 'discoverable' ) {
1004+ await this . _create ( { strategy : 'passkey' } ) ;
1005+ } else {
1006+ const passKeyFactor = this . supportedFirstFactors . find ( f => f . strategy === 'passkey' ) as PasskeyFactor ;
1007+
1008+ if ( ! passKeyFactor ) {
1009+ clerkVerifyPasskeyCalledBeforeCreate ( ) ;
1010+ }
1011+ await this . resource . __internal_basePost ( {
1012+ body : { strategy : 'passkey' } ,
1013+ action : 'prepare_first_factor' ,
1014+ } ) ;
1015+ }
1016+
1017+ const { nonce } = this . firstFactorVerification ;
1018+ const publicKeyOptions = nonce ? convertJSONToPublicKeyRequestOptions ( JSON . parse ( nonce ) ) : null ;
1019+
1020+ if ( ! publicKeyOptions ) {
1021+ clerkMissingWebAuthnPublicKeyOptions ( 'get' ) ;
1022+ }
1023+
1024+ let canUseConditionalUI = false ;
1025+
1026+ if ( flow === 'autofill' ) {
1027+ /**
1028+ * If autofill is not supported gracefully handle the result, we don't need to throw.
1029+ * The caller should always check this before calling this method.
1030+ */
1031+ canUseConditionalUI = await isWebAuthnAutofillSupported ( ) ;
1032+ }
1033+
1034+ // Invoke the navigator.create.get() method.
1035+ const { publicKeyCredential, error } = await webAuthnGetCredential ( {
1036+ publicKeyOptions,
1037+ conditionalUI : canUseConditionalUI ,
1038+ } ) ;
1039+
1040+ if ( ! publicKeyCredential ) {
1041+ throw error ;
1042+ }
1043+
1044+ await this . resource . __internal_basePost ( {
1045+ body : {
1046+ publicKeyCredential : JSON . stringify ( serializePublicKeyCredentialAssertion ( publicKeyCredential ) ) ,
1047+ strategy : 'passkey' ,
1048+ } ,
1049+ action : 'attempt_first_factor' ,
1050+ } ) ;
1051+ } ) ;
1052+ }
1053+
9821054 async sendMFAPhoneCode ( ) : Promise < { error : unknown } > {
9831055 return runAsyncResourceTask ( this . resource , async ( ) => {
9841056 const phoneCodeFactor = this . resource . supportedSecondFactors ?. find ( f => f . strategy === 'phone_code' ) ;
0 commit comments