diff --git a/docs/custom-flows/passkeys.mdx b/docs/custom-flows/passkeys.mdx index e44ed9b25c..a9144f325f 100644 --- a/docs/custom-flows/passkeys.mdx +++ b/docs/custom-flows/passkeys.mdx @@ -22,72 +22,62 @@ To use passkeys, first enable the strategy in the Clerk Dashboard. To create a passkey for a user, you must call [`User.createPasskey()`](/docs/references/javascript/user/user#create-passkey), as shown in the following example: - - ```tsx {{ filename: 'app/components/CustomCreatePasskeysButton.tsx' }} - 'use client' - - import { useUser } from '@clerk/nextjs' - - export function CreatePasskeyButton() { - const { user } = useUser() - - const createClerkPasskey = async () => { - try { - const response = await user?.createPasskey() - console.log(response) - } catch (err) { - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error('Error:', JSON.stringify(err, null, 2)) - } +```tsx {{ filename: 'app/components/CustomCreatePasskeysButton.tsx' }} +export function CreatePasskeyButton() { + const { user } = useUser() + + const createClerkPasskey = async () => { + if (!user) return + + try { + const response = await user?.createPasskey() + console.log(response) + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) } - - return } - ``` - + + return +} +``` ## Sign a user in with a passkey To sign a user into your Clerk app with a passkey, you must call [`SignIn.authenticateWithPasskey()`](/docs/references/javascript/sign-in/authenticate-with#authenticate-with-passkey). This method allows users to choose from their discoverable passkeys, such as hardware keys or passkeys in password managers. - - ```tsx {{ filename: 'components/SignInWithPasskeyButton.tsx' }} - 'use client' - - import { useSignIn } from '@clerk/nextjs' - import { useRouter } from 'next/navigation' - - export function SignInWithPasskeyButton() { - const { signIn } = useSignIn() - const router = useRouter() - - const signInWithPasskey = async () => { - // 'discoverable' lets the user choose a passkey - // without autofilling any of the options - try { - const signInAttempt = await signIn?.authenticateWithPasskey({ - flow: 'discoverable', - }) - - if (signInAttempt?.status === 'complete') { - router.push('/') - } else { - // If the status is not complete, check why. User may need to - // complete further steps. - console.error(JSON.stringify(signInAttempt, null, 2)) - } - } catch (err) { - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error('Error:', JSON.stringify(err, null, 2)) +```tsx {{ filename: 'components/SignInWithPasskeyButton.tsx' }} +export function SignInWithPasskeyButton() { + const { signIn } = useSignIn() + const router = useRouter() + + const signInWithPasskey = async () => { + // 'discoverable' lets the user choose a passkey + // without autofilling any of the options + try { + const signInAttempt = await signIn?.authenticateWithPasskey({ + flow: 'discoverable', + }) + + if (signInAttempt?.status === 'complete') { + await setActive({ session: signInAttempt.createdSessionId }) + router.push('/') + } else { + // If the status is not complete, check why. User may need to + // complete further steps. + console.error(JSON.stringify(signInAttempt, null, 2)) } + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) } - - return } - ``` - + + return +} +``` ## Rename user passkeys @@ -95,109 +85,94 @@ Clerk generates a name based on the device associated with the passkey when it's To rename a user's passkey in your Clerk app, you must call the [`update()`](/docs/references/javascript/types/passkey-resource#update) method of the passkey object, as shown in the following example: - - ```tsx {{ filename: 'components/RenamePasskeyUI.tsx' }} - 'use client' - - import { useUser } from '@clerk/nextjs' - import { PasskeyResource } from '@clerk/types' - import { useRef, useState } from 'react' - - export function RenamePasskeyUI() { - const { user } = useUser() - const passkeyToUpdateId = useRef(null) - const newPasskeyName = useRef(null) - const { passkeys } = user - const [success, setSuccess] = useState(false) - - const renamePasskey = async () => { - try { - const passkeyToUpdate = passkeys?.find( - (pk: PasskeyResource) => pk.id === passkeyToUpdateId.current?.value, - ) - const response = await passkeyToUpdate?.update({ - name: newPasskeyName.current?.value, - }) - console.log(response) - setSuccess(true) - } catch (err) { - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error('Error:', JSON.stringify(err, null, 2)) - setSuccess(false) - } +```tsx {{ filename: 'components/RenamePasskeyUI.tsx' }} +export function RenamePasskeyUI() { + const { user } = useUser() + const passkeyToUpdateId = useRef(null) + const newPasskeyName = useRef(null) + const { passkeys } = user + const [success, setSuccess] = useState(false) + + const renamePasskey = async () => { + try { + const passkeyToUpdate = passkeys?.find( + (pk: PasskeyResource) => pk.id === passkeyToUpdateId.current?.value, + ) + const response = await passkeyToUpdate?.update({ + name: newPasskeyName.current?.value, + }) + console.log(response) + setSuccess(true) + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) + setSuccess(false) } - - return ( - <> -

Passkeys:

-
    - {passkeys?.map((pk: PasskeyResource) => { - return ( -
  • - Name: {pk.name} | ID: {pk.id} -
  • - ) - })} -
- - - -

Passkey updated: {success ? 'Yes' : 'No'}

- - ) } - ``` -
+ + return ( + <> +

Passkeys:

+ + + + +

Passkey updated: {success ? 'Yes' : 'No'}

+ + ) +} +``` ## Delete user passkeys To delete a user's passkey from your Clerk app, you must call the [`delete()`](/docs/references/javascript/types/passkey-resource#delete) method of the passkey object, as shown in the following example: - - ```tsx {{ filename: 'components/DeletePasskeyUI.tsx' }} - 'use client' - - import { useUser } from '@clerk/nextjs' - import { useRef, useState } from 'react' - - export function DeletePasskeyUI() { - const { user } = useUser() - const passkeyToDeleteId = useRef(null) - const { passkeys } = user - const [success, setSuccess] = useState(false) - - const deletePasskey = async () => { - const passkeyToDelete = passkeys?.find((pk: any) => pk.id === passkeyToDeleteId.current?.value) - try { - const response = await passkeyToDelete?.delete() - console.log(response) - setSuccess(true) - } catch (err) { - // See https://clerk.com/docs/custom-flows/error-handling - // for more info on error handling - console.error('Error:', JSON.stringify(err, null, 2)) - setSuccess(false) - } +```tsx {{ filename: 'components/DeletePasskeyUI.tsx' }} +export function DeletePasskeyUI() { + const { user } = useUser() + const passkeyToDeleteId = useRef(null) + const { passkeys } = user + const [success, setSuccess] = useState(false) + + const deletePasskey = async () => { + const passkeyToDelete = passkeys?.find((pk: any) => pk.id === passkeyToDeleteId.current?.value) + try { + const response = await passkeyToDelete?.delete() + console.log(response) + setSuccess(true) + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error('Error:', JSON.stringify(err, null, 2)) + setSuccess(false) } - - return ( - <> -

Passkeys:

-
    - {passkeys?.map((pk: any) => { - return ( -
  • - Name: {pk.name} | ID: {pk.id} -
  • - ) - })} -
- - -

Passkey deleted: {success ? 'Yes' : 'No'}

- - ) } - ``` -
+ + return ( + <> +

Passkeys:

+ + + +

Passkey deleted: {success ? 'Yes' : 'No'}

+ + ) +} +``` diff --git a/docs/manifest.json b/docs/manifest.json index 4ab96626aa..3525f095cf 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -2141,6 +2141,10 @@ { "title": "Use biometrics with local credentials", "href": "/docs/references/expo/local-credentials" + }, + { + "title": "Configure passkeys", + "href": "/docs/references/expo/passkeys" } ] ] diff --git a/docs/references/expo/passkeys.mdx b/docs/references/expo/passkeys.mdx new file mode 100644 index 0000000000..9c9ef9034a --- /dev/null +++ b/docs/references/expo/passkeys.mdx @@ -0,0 +1,185 @@ +--- +title: Configure passkeys for Expo +description: Learn how to configure passkeys for your Expo application. +--- + +This guide shows you how to configure passkeys for your Expo application. + +> [!WARNING] +> This API is available only for [@clerk/clerk-expo v2](/docs/upgrade-guides/expo-v2/upgrade) >=2.2.0. + +## Configuration + + + ### Enable passkeys + + To use passkeys, first enable the strategy in the Clerk Dashboard. + + 1. Navigate to the [Clerk Dashboard](https://dashboard.clerk.com/last-active?path=user-authentication/email-phone-username). + 1. In the navigation sidebar, select **User & Authentication > Email, Phone, and Username**. + 1. In the **Authentication strategies** section of this page, ensure **Passkeys** is enabled. + + ### Configure passkeys for iOS and Android + + Use the following tabs to configure your passkeys for `iOS` or `Android`. + + + + + > [!WARNING] + > iOS supports passkeys from version iOS 16+ + + ### Get your App ID Prefix and Bundle ID from Apple + + To get your **App ID Prefix** and **Bundle ID**, follow these steps: + + 1. Navigate to the [Apple Developer dashboard](https://developer.apple.com/account). + 1. Under **Certificates, IDs and Profiles**, select [**Identifiers**](https://developer.apple.com/account/resources/identifiers/list). + 1. In the top-right, select the dropdown and select **App IDs**. + 1. Select the **App ID** you want to configure passkeys for. You'll be redirect to the **Edit your App ID Configuration** page. + 1. At the top of the page, you'll see your **App ID Prefix** and **Bundle ID**. Save these values somewhere secure. + + ### Set up your associated domain file in the Clerk Dashboard + + 1. In the Clerk Dashboard, navigate to the [**Native Applications**](https://dashboard.clerk.com/last-active?path=native-applications) page. + 1. Paste your **App ID Prefix** and **Bundle ID** into the respective fields. You'll be redirected to the **iOS association file**. + 1. Look for the text that says "The association file has been hosted at" followed by a URL. This URL is your Clerk **Frontend API URL** - copy it as you'll need it in the next step. + + ### Update `app.json` in your Expo app + + 1. In your app's `app.json` file, under the `ios` object, add the `associatedDomains` property as shown in the following example. Replace `` with the value that you copied in the previous step. + + ```json {{ filename: 'app.json', mark: [[14, 20]] }} + { + "expo": { + //...existing properties + "plugins": [ + [ + "expo-build-properties", + { + "ios": { + "deploymentTarget": "16.0" // iOS Support passkeys from version iOS 16+ + } + } + ] + ], + "ios": { + //...existing properties + "associatedDomains": [ + "applinks:", + "webcredentials:" + ] + } + } + } + ``` + + + + + + > [!WARNING] + > Android supports passkeys from version 9+ + + ### Set up your Android app links in the Clerk Dashboard + + 1. In the Clerk Dashboard, navigate to the [**Native Applications**](https://dashboard.clerk.com/last-active?path=native-applications) page. + 1. Select the **Android** tab. + 1. Fill out the form with the following information: + - The `namespace` (use the default value: `android_app`) + - Your Android app's package name + - The `SHA256 certificate fingerprints`. If you don't know where to find the `SHA256 certificate fingerprints`, see the [Expo docs](https://docs.expo.dev/linking/android-app-links/#create-assetlinksjson-file). + 1. After submitting the form, you'll be redirected to **Asset links**. You can verify that your `assetlinks.json` file is properly associated with your app by using [Google's **Statement List Generator and Tester**](https://developers.google.com/digital-asset-links/tools/generator). + 1. Look for the text that says "The assetlinks.json has been hosted at" followed by a URL. This URL is your Clerk **Frontend API URL** - copy it as you'll need it in the next step. + + ### Update `app.json` in your Expo application + + 1. In your app's `app.json` file, under `android`, add the `intentFilters` property as shown in the following example. Replace `` with the value that you copied in the previous step. + + ```json {{ filename: 'app.json', mark: [[5, 14]] }} + { + "expo": { + "android": { + //...existing properties + "intentFilters": [ + { + "action": "VIEW", + "autoVerify": true, + "data": [ + { + "scheme": "https", + "host": "" + } + ], + "category": ["BROWSABLE", "DEFAULT"] + } + ] + } + } + } + ``` + + + + + ### Prebuild Expo project + + Run the following command to prebuild your Expo project: + + ```bash {{ filename: 'terminal' }} + npx expo prebuild + ``` + + ### Install `@clerk/expo-passkeys` + + Run the following command to install the `@clerk/expo-passkeys` package: + + + ```bash {{ filename: 'terminal' }} + npm install @clerk/expo-passkeys + ``` + + ```bash {{ filename: 'terminal' }} + yarn add @clerk/expo-passkeys + ``` + + ```bash {{ filename: 'terminal' }} + pnpm add @clerk/expo-passkeys + ``` + + + ### Update your `` + + Pass the `passkeys` object to the `__experimental__passkeys` property of your `` component, as shown in the following example: + + ```tsx {{ filename: 'app/_layout.tsx', mark: [3, 17] }} + import { tokenCache } from '@/cache' + import { ClerkProvider, ClerkLoaded } from '@clerk/clerk-expo' + import { passkeys } from '@clerk/clerk-expo/passkeys' + import { Slot } from 'expo-router' + + export default function RootLayout() { + const publishableKey = process.env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY! + + if (!publishableKey) { + throw new Error('Add EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY in your .env') + } + + return ( + + + + + + ) + } + ``` + + +## Usage + +To learn how to use passkeys in your Expo application, such as creating, deleting, and authenticating users with passkeys, see the [custom flow guide](/docs/custom-flows/passkeys).