Skip to content

Commit 678cf40

Browse files
committed
Implement passkey login hook and call it when the contact-info component renders
1 parent ce6ea32 commit 678cf40

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

packages/commerce-sdk-react/src/auth/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1532,12 +1532,14 @@ class Auth {
15321532
// Required params
15331533
client_id: parameters.client_id || slasClient.clientConfig.parameters.clientId,
15341534
channel_id: parameters.channel_id || slasClient.clientConfig.parameters.siteId,
1535-
user_id: parameters.user_id,
15361535
// Optional params
1536+
...(parameters.user_id && {user_id: parameters.user_id}),
15371537
...(parameters.tenant_id && {tenant_id: parameters.tenant_id})
15381538
}
15391539
}
15401540

1541+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1542+
// @ts-ignore TODO: user_id is optional, but commerce-sdk-isomorphic expects it to be required. Remove this comment after commerce-sdk-isomorphic is updated.
15411543
return await slasClient.startWebauthnAuthentication(options)
15421544
}
15431545

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (c) 2024, Salesforce, Inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: BSD-3-Clause
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6+
*/
7+
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
8+
import {useAuthHelper, AuthHelpers} from '@salesforce/commerce-sdk-react'
9+
10+
/**
11+
* This hook provides commerce-react-sdk hooks to simplify the passkey login flow.
12+
*/
13+
export const usePasskeyLogin = () => {
14+
const startWebauthnAuthentication = useAuthHelper(AuthHelpers.StartWebauthnAuthentication)
15+
const startPasskeyLogin = async () => {
16+
const config = getConfig()
17+
18+
// Check if passkey is enabled in config
19+
if (!config?.app?.login?.passkey?.enabled) {
20+
return
21+
}
22+
23+
// Availability of window.PublicKeyCredential means WebAuthn is usable
24+
if (
25+
!window.PublicKeyCredential ||
26+
!window.PublicKeyCredential.isConditionalMediationAvailable
27+
) {
28+
return
29+
}
30+
31+
// Check if conditional mediation is available. Conditional mediation is a feature
32+
// that allows the browser to prompt the user for a password only if the user has
33+
// not already authenticated with the same device in the current session.
34+
const isCMA = await window.PublicKeyCredential.isConditionalMediationAvailable()
35+
if (!isCMA) {
36+
return
37+
}
38+
39+
try {
40+
const startResponse = await startWebauthnAuthentication.mutateAsync({})
41+
console.log('startResponse ->', startResponse)
42+
return startResponse
43+
} catch (error) {
44+
console.error('Error starting passkey authentication:', error)
45+
}
46+
}
47+
48+
return {startPasskeyLogin}
49+
}

packages/template-retail-react-app/app/pages/checkout/partials/contact-info.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {useAppOrigin} from '@salesforce/retail-react-app/app/hooks/use-app-origi
4646
import {AuthHelpers, useAuthHelper, useShopperBasketsMutation} from '@salesforce/commerce-sdk-react'
4747
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
4848
import {getEnvBasePath} from '@salesforce/pwa-kit-runtime/utils/ssr-namespace-paths'
49+
import {usePasskeyLogin} from '@salesforce/retail-react-app/app/hooks/use-passkey-login'
4950
import {
5051
API_ERROR_MESSAGE,
5152
FEATURE_UNAVAILABLE_ERROR_MESSAGE,
@@ -63,6 +64,7 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id
6364
const authorizePasswordlessLogin = useAuthHelper(AuthHelpers.AuthorizePasswordless)
6465
const updateCustomerForBasket = useShopperBasketsMutation('updateCustomerForBasket')
6566
const mergeBasket = useShopperBasketsMutation('mergeBasket')
67+
const {startPasskeyLogin} = usePasskeyLogin()
6668

6769
const {step, STEPS, goToStep, goToNextStep} = useCheckout()
6870

@@ -79,7 +81,8 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id
7981

8082
const [authModalView, setAuthModalView] = useState(PASSWORD_VIEW)
8183
const authModal = useAuthModal(authModalView)
82-
const passwordlessConfigCallback = getConfig().app.login?.passwordless?.callbackURI
84+
const config = getConfig()
85+
const passwordlessConfigCallback = config.app.login?.passwordless?.callbackURI
8386
const callbackURL = isAbsoluteURL(passwordlessConfigCallback)
8487
? passwordlessConfigCallback
8588
: `${appOrigin}${getEnvBasePath()}${passwordlessConfigCallback}`
@@ -157,6 +160,10 @@ const ContactInfo = ({isSocialEnabled = false, isPasswordlessEnabled = false, id
157160
}
158161
}, [showPasswordField])
159162

163+
useEffect(() => {
164+
startPasskeyLogin()
165+
}, [])
166+
160167
const onPasswordlessLoginClick = async (e) => {
161168
const isValid = await form.trigger('email')
162169
const domForm = e.target.closest('form')

0 commit comments

Comments
 (0)