Passkey Authentication for Salesforce Digital Experience (Community Cloud) — Part 2
In Part 1 we covered the passkey registration flow. With user credentials now saved in Salesforce, let's walk through how to authenticate with them.
Triggering authentication
Call navigator.credentials.get(options) with the publicKey parameter and the following options:
- challenge — a randomly generated string signed by the user's device.
- timeout — after which passkey authentication is cancelled automatically.
- allowCredentials — restricts acceptable credentials; for B2C users leave the array empty.
- userVerification — indicates whether the user must pass biometrics.
- rpId — your community host, taken from the experienceInfo used during registration.
Generating a challenge for guest users
During registration the challenge was easy: we had a user session and could use Platform Cache, which does not work in guest user context. Storing random strings in a custom object means managing invalidation and cleanup — there is a better way.
WebAuthn depends on randomised challenges to avoid replay attacks: both PublicKeyCredentialCreationOptions.challenge and PublicKeyCredentialRequestOptions.challenge must be randomly generated server-side, and the returned value must match what was generated.
reCAPTCHA tokens as challenges
A reCAPTCHA response token is valid for two minutes and can be verified only once — exactly the replay protection a challenge needs. Add reCAPTCHA to the community head markup, subscribe to the grecaptchaVerified event in your LWC, and use the verified token as the challenge for the passkey assertion.
Apex then verifies the signed challenge against the stored public key and logs the user in, giving Experience Cloud a genuinely passwordless login option.