diff --git a/.changeset/quiet-firefox-passkeys.md b/.changeset/quiet-firefox-passkeys.md new file mode 100644 index 00000000000..5820b8c47dd --- /dev/null +++ b/.changeset/quiet-firefox-passkeys.md @@ -0,0 +1,5 @@ +--- +'@clerk/ui': patch +--- + +Prevent passkey autofill from displaying an RP ID or domain error when a browser rejects a background credential request. diff --git a/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx index 36a0b24858b..01bd5d4e3a0 100644 --- a/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx +++ b/packages/ui/src/components/SignIn/__tests__/SignInStart.test.tsx @@ -1,4 +1,4 @@ -import { ClerkAPIResponseError } from '@clerk/shared/error'; +import { ClerkAPIResponseError, ClerkWebAuthnError } from '@clerk/shared/error'; import { CAPTCHA_ELEMENT_ID } from '@clerk/shared/internal/clerk-js/constants'; import { OAUTH_PROVIDERS } from '@clerk/shared/oauth'; import type { SignInResource } from '@clerk/shared/types'; @@ -154,6 +154,32 @@ describe('SignInStart', () => { }); }); + it('does not display related-origin errors from passkey autofill', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withEmailAddress(); + f.withPasskey(); + f.withPasskeySettings({ + allow_autofill: true, + show_sign_in_button: true, + }); + }); + + fixtures.signIn.authenticateWithPasskey.mockRejectedValue( + new ClerkWebAuthnError('The operation is insecure.', { + code: 'passkey_invalid_rpID_or_domain', + }), + ); + render(, { wrapper }); + + await waitFor(() => { + expect(fixtures.signIn.authenticateWithPasskey).toHaveBeenCalledWith({ + flow: 'autofill', + }); + }); + expect(screen.queryByText(/operation is insecure/i)).not.toBeInTheDocument(); + screen.getByText('Use passkey instead'); + }); + it('skips autofill when the host reports no autofill support', async () => { const { wrapper, fixtures } = await createFixtures(f => { f.withEmailAddress(); diff --git a/packages/ui/src/components/SignIn/shared.ts b/packages/ui/src/components/SignIn/shared.ts index 33cb2026be8..ec23c4fff39 100644 --- a/packages/ui/src/components/SignIn/shared.ts +++ b/packages/ui/src/components/SignIn/shared.ts @@ -70,8 +70,11 @@ function useHandleAuthenticateWithPasskey( if (err.code === 'passkey_operation_aborted') { return; } - // In case of autofill, if retrieval of credentials is cancelled by the user avoid showing errors as it results to pour UX. - if (flow === 'autofill' && err.code === 'passkey_retrieval_cancelled') { + // Autofill runs in the background, so browser rejections must not surface as form errors. + if ( + flow === 'autofill' && + (err.code === 'passkey_retrieval_cancelled' || err.code === 'passkey_invalid_rpID_or_domain') + ) { return; } }