Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ntitoras/user 985 add expo passkeys guide #1702

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,6 @@ A passkey is a type of sign-in credential that requires one user action, but use
1. A pin number or biometric data
1. A physical device

Passkeys are the most secure **passwordless** strategy because they use two factors.

Users can only create passkeys after signing up, so you'll need to enable another authentication strategy for the sign-up process. After signing in, users can create a passkey.

#### Manage user passkeys
Expand Down
283 changes: 130 additions & 153 deletions docs/custom-flows/passkeys.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ description: Learn how to use the Clerk API to build a custom authentication flo

<Include src="_partials/custom-flows-callout" />

Clerk supports passwordless authentication via passkeys, enabling users to sign in without having to remember a password. Instead, users select a passkey associated with their device, which they can use to authenticate themselves.
Clerk supports passwordless authentication via [passkeys](/docs/authentication/configuration/sign-up-sign-in-options#passkeys), enabling users to sign in without having to remember a password. Instead, users select a passkey associated with their device, which they can use to authenticate themselves.

This guide will walk you through how to use the Clerk API to build custom flows for creating, signing users in with, and managing passkeys.
This guide demonstrates how to use the Clerk API to build a custom user interface for creating, signing users in with, and managing passkeys.

## Enable passkeys

Expand All @@ -24,182 +24,159 @@ 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:

<CodeBlockTabs options={["Next.js"]}>
```tsx {{ filename: 'app/components/CustomCreatePasskeysButton.tsx' }}
'use client'
```tsx {{ filename: 'app/components/CustomCreatePasskeysButton.tsx' }}
export function CreatePasskeyButton() {
const { user } = useUser()

import { useUser } from '@clerk/nextjs'
const createClerkPasskey = async () => {
if (!user) return

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))
}
try {
await user?.createPasskey()
} 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 <button onClick={createClerkPasskey}>Create a passkey now</button>
}
```
</CodeBlockTabs>

return <button onClick={createClerkPasskey}>Create a passkey</button>
}
```

## 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.

<CodeBlockTabs options={["Next.js"]}>
```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 auto-filling 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 <button onClick={signInWithPasskey}>Sign in with a passkey</button>
}
```
</CodeBlockTabs>

return <button onClick={signInWithPasskey}>Sign in with a passkey</button>
}
```

## Rename user passkeys

Clerk generates a name based on the device associated with the passkey when it's created. Sometimes users may want to rename a passkey to make it easier to identify.

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:

<CodeBlockTabs options={["Next.js"]}>
```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<HTMLInputElement>(null)
const newPasskeyName = useRef<HTMLInputElement>(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 { passkeys } = user

const passkeyToUpdateId = useRef<HTMLInputElement>(null)
const newPasskeyName = useRef<HTMLInputElement>(null)
const [success, setSuccess] = useState(false)

const renamePasskey = async () => {
try {
const passkeyToUpdate = passkeys?.find(
(pk: PasskeyResource) => pk.id === passkeyToUpdateId.current?.value,
)

await passkeyToUpdate?.update({
name: newPasskeyName.current?.value,
})

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 (
<>
<p>Passkeys:</p>
<ul>
{passkeys?.map((pk: PasskeyResource) => {
return (
<li key={pk.id}>
Name: {pk.name} | ID: {pk.id}
</li>
)
})}
</ul>
<input ref={passkeyToUpdateId} type="text" placeholder="ID of passkey to update" />
<input type="text" placeholder="New name" ref={newPasskeyName} />
<button onClick={renamePasskey}>Rename your passkey</button>
<p>Passkey updated: {success ? 'Yes' : 'No'}</p>
</>
)
}
```
</CodeBlockTabs>

return (
<>
<p>Passkeys:</p>
<ul>
{passkeys?.map((pk: PasskeyResource) => {
return (
<li key={pk.id}>
Name: {pk.name} | ID: {pk.id}
</li>
)
})}
</ul>
<input ref={passkeyToUpdateId} type="text" placeholder="Enter the passkey ID" />
<input type="text" placeholder="Enter the passkey's new name" ref={newPasskeyName} />
<button onClick={renamePasskey}>Rename passkey</button>
<p>Passkey updated: {success ? 'Yes' : 'No'}</p>
</>
)
}
```

## 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:

<CodeBlockTabs options={["Next.js"]}>
```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<HTMLInputElement>(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 { passkeys } = user

const passkeyToDeleteId = useRef<HTMLInputElement>(null)
const [success, setSuccess] = useState(false)

const deletePasskey = async () => {
const passkeyToDelete = passkeys?.find((pk: any) => pk.id === passkeyToDeleteId.current?.value)
try {
await passkeyToDelete?.delete()

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 (
<>
<p>Passkeys:</p>
<ul>
{passkeys?.map((pk: any) => {
return (
<li key={pk.id}>
Name: {pk.name} | ID: {pk.id}
</li>
)
})}
</ul>
<input ref={passkeyToDeleteId} type="text" placeholder="ID of passkey to delete" />
<button onClick={deletePasskey}>Delete passkey</button>
<p>Passkey deleted: {success ? 'Yes' : 'No'}</p>
</>
)
}
```
</CodeBlockTabs>

return (
<>
<p>Passkeys:</p>
<ul>
{passkeys?.map((pk: any) => {
return (
<li key={pk.id}>
Name: {pk.name} | ID: {pk.id}
</li>
)
})}
</ul>
<input ref={passkeyToDeleteId} type="text" placeholder="Enter the passkey ID" />
<button onClick={deletePasskey}>Delete passkey</button>
<p>Passkey deleted: {success ? 'Yes' : 'No'}</p>
</>
)
}
```
4 changes: 4 additions & 0 deletions docs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,10 @@
"title": "Use biometrics with local credentials",
"href": "/docs/references/expo/local-credentials"
},
{
"title": "Configure passkeys",
"href": "/docs/references/expo/passkeys"
},
{
"title": "Offline support",
"href": "/docs/references/expo/offline-support"
Expand Down
Loading