diff --git a/.changeset/mobile-631-passkey-second-factor.md b/.changeset/mobile-631-passkey-second-factor.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/mobile-631-passkey-second-factor.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/expo/src/passkeys/index.ts b/packages/expo/src/passkeys/index.ts index 5c012765c61..b2fc418a921 100644 --- a/packages/expo/src/passkeys/index.ts +++ b/packages/expo/src/passkeys/index.ts @@ -1,3 +1,4 @@ import { passkeys } from '@clerk/expo-passkeys'; +/** Native passkey adapter for sign-in factors and session reverification. */ export { passkeys }; diff --git a/packages/expo/src/provider/singleton/__tests__/createClerkInstance.test.ts b/packages/expo/src/provider/singleton/__tests__/createClerkInstance.test.ts index 6b3324ae8a3..4f08c16bedd 100644 --- a/packages/expo/src/provider/singleton/__tests__/createClerkInstance.test.ts +++ b/packages/expo/src/provider/singleton/__tests__/createClerkInstance.test.ts @@ -1,5 +1,6 @@ import type { Clerk } from '@clerk/clerk-js'; import { ClerkRuntimeError } from '@clerk/shared/error'; +import type { PublicKeyCredentialRequestOptionsWithoutExtensions } from '@clerk/shared/types'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { DUMMY_CLERK_CLIENT_RESOURCE, DUMMY_CLERK_ENVIRONMENT_RESOURCE } from '../../../cache'; @@ -141,6 +142,39 @@ describe('createClerkInstance', () => { expect(mocks.constructorSpy).toHaveBeenCalledTimes(1); }); + test('routes passkey assertion requests for sign-in and reverification through the native adapter', async () => { + const createClerkInstance = await loadCreateClerkInstance(); + const getClerkInstance = createClerkInstance(MockClerk as unknown as typeof Clerk); + const get = vi.fn().mockResolvedValue({ publicKeyCredential: null, error: null }); + const clerk = getClerkInstance({ + publishableKey: 'pk_test_123', + __experimental_passkeys: { + get, + create: vi.fn(), + isSupported: vi.fn(() => true), + isAutoFillSupported: vi.fn(() => Promise.resolve(false)), + }, + }) as unknown as MockClerk & { + __internal_getPublicCredentials: (params: { + publicKeyOptions: PublicKeyCredentialRequestOptionsWithoutExtensions; + }) => Promise; + }; + const signInSecondFactorOptions = { + challenge: new Uint8Array([1]).buffer, + rpId: 'example.com', + } as PublicKeyCredentialRequestOptionsWithoutExtensions; + const sessionSecondFactorOptions = { + challenge: new Uint8Array([2]).buffer, + rpId: 'example.com', + } as PublicKeyCredentialRequestOptionsWithoutExtensions; + + await clerk.__internal_getPublicCredentials({ publicKeyOptions: signInSecondFactorOptions }); + await clerk.__internal_getPublicCredentials({ publicKeyOptions: sessionSecondFactorOptions }); + + expect(get).toHaveBeenNthCalledWith(1, { publicKeyOptions: signInSecondFactorOptions }); + expect(get).toHaveBeenNthCalledWith(2, { publicKeyOptions: sessionSecondFactorOptions }); + }); + test('recreates the singleton when proxyUrl changes', async () => { const createClerkInstance = await loadCreateClerkInstance(); const getClerkInstance = createClerkInstance(MockClerk as unknown as typeof Clerk); diff --git a/packages/expo/src/provider/singleton/types.ts b/packages/expo/src/provider/singleton/types.ts index f3da41233d7..ea83ee912d3 100644 --- a/packages/expo/src/provider/singleton/types.ts +++ b/packages/expo/src/provider/singleton/types.ts @@ -26,6 +26,10 @@ export type BuildClerkOptions = { * @experimental This API is experimental and may change at any moment. */ __experimental_passkeys?: { + /** + * Requests a native passkey assertion. This adapter is shared by first-factor sign-in, + * second-factor sign-in, and session reverification flows. + */ get: ({ publicKeyOptions, }: {