Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/mobile-631-passkey-second-factor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
1 change: 1 addition & 0 deletions packages/expo/src/passkeys/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { passkeys } from '@clerk/expo-passkeys';

/** Native passkey adapter for sign-in factors and session reverification. */
export { passkeys };
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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<unknown>;
};
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);
Expand Down
4 changes: 4 additions & 0 deletions packages/expo/src/provider/singleton/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}: {
Expand Down
Loading