@@ -5,6 +5,7 @@ import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
5
5
import { ERROR_CODES } from '../../../core/constants' ;
6
6
import { clerkInvalidFAPIResponse } from '../../../core/errors' ;
7
7
import { getClerkQueryParam , removeClerkQueryParam } from '../../../utils' ;
8
+ import { isWebAuthnAutofillSupported , isWebAuthnSupported } from '../../../utils/passkeys' ;
8
9
import type { SignInStartIdentifier } from '../../common' ;
9
10
import { getIdentifierControlDisplayValues , groupIdentifiers , withRedirectToAfterSignIn } from '../../common' ;
10
11
import { buildSSOCallbackURL } from '../../common/redirects' ;
@@ -24,8 +25,36 @@ import { useSupportEmail } from '../../hooks/useSupportEmail';
24
25
import { useRouter } from '../../router' ;
25
26
import type { FormControlState } from '../../utils' ;
26
27
import { buildRequest , handleError , isMobileDevice , useFormControl } from '../../utils' ;
28
+ import { useHandleAuthenticateWithPasskey } from './shared' ;
27
29
import { SignInSocialButtons } from './SignInSocialButtons' ;
28
30
31
+ const useAutoFillPasskey = ( ) => {
32
+ const [ isSupported , setIsSupported ] = useState ( false ) ;
33
+ const authenticateWithPasskey = useHandleAuthenticateWithPasskey ( ) ;
34
+ const { userSettings } = useEnvironment ( ) ;
35
+ const { passkeySettings } = userSettings ;
36
+
37
+ useEffect ( ( ) => {
38
+ async function runAutofillPasskey ( ) {
39
+ const _isSupported = await isWebAuthnAutofillSupported ( ) ;
40
+ setIsSupported ( _isSupported ) ;
41
+ if ( ! _isSupported ) {
42
+ return ;
43
+ }
44
+
45
+ await authenticateWithPasskey ( { flow : 'autofill' } ) ;
46
+ }
47
+
48
+ if ( passkeySettings . allow_autofill ) {
49
+ runAutofillPasskey ( ) ;
50
+ }
51
+ } , [ ] ) ;
52
+
53
+ return {
54
+ isWebAuthnAutofillSupported : isSupported ,
55
+ } ;
56
+ } ;
57
+
29
58
export function _SignInStart ( ) : JSX . Element {
30
59
const card = useCardState ( ) ;
31
60
const clerk = useClerk ( ) ;
@@ -41,6 +70,13 @@ export function _SignInStart(): JSX.Element {
41
70
[ userSettings . enabledFirstFactorIdentifiers ] ,
42
71
) ;
43
72
73
+ /**
74
+ * Passkeys
75
+ */
76
+ const { isWebAuthnAutofillSupported } = useAutoFillPasskey ( ) ;
77
+ const authenticateWithPasskey = useHandleAuthenticateWithPasskey ( ) ;
78
+ const isWebSupported = isWebAuthnSupported ( ) ;
79
+
44
80
const onlyPhoneNumberInitialValueExists =
45
81
! ! ctx . initialValues ?. phoneNumber && ! ( ctx . initialValues . emailAddress || ctx . initialValues . username ) ;
46
82
const shouldStartWithPhoneNumberIdentifier =
@@ -318,6 +354,7 @@ export function _SignInStart(): JSX.Element {
318
354
onActionClicked = { switchToNextIdentifier }
319
355
{ ...identifierFieldProps }
320
356
autoFocus = { shouldAutofocus }
357
+ autoComplete = { isWebAuthnAutofillSupported ? 'webauthn' : undefined }
321
358
/>
322
359
</ Form . ControlRow >
323
360
< InstantPasswordRow field = { passwordBasedInstance ? instantPasswordField : undefined } />
@@ -326,9 +363,16 @@ export function _SignInStart(): JSX.Element {
326
363
</ Form . Root >
327
364
) : null }
328
365
</ SocialButtonsReversibleContainerWithDivider >
366
+ { userSettings . passkeySettings . show_sign_in_button && isWebSupported && (
367
+ < Card . Action elementId = { 'usePasskey' } >
368
+ < Card . ActionLink
369
+ localizationKey = { localizationKeys ( 'signIn.start.actionLink__use_passkey' ) }
370
+ onClick = { ( ) => authenticateWithPasskey ( { flow : 'discoverable' } ) }
371
+ />
372
+ </ Card . Action >
373
+ ) }
329
374
</ Col >
330
375
</ Card . Content >
331
-
332
376
< Card . Footer >
333
377
< Card . Action elementId = 'signIn' >
334
378
< Card . ActionText localizationKey = { localizationKeys ( 'signIn.start.actionText' ) } />
0 commit comments