āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/clerk/clerk-docs/guides/development/custom-flows/authentication/passkeys ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
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.
This guide demonstrates how to use the Clerk API to build a custom user interface for creating, signing users in with, and managing passkeys.
To use passkeys, first enable the strategy in the Clerk Dashboard.
<If sdk="android"> When setting up passkeys with Android, there are a few additional steps to follow. [Learn more about passkeys for Android](/docs/reference/android/passkeys). </If> <If sdk="expo"> When setting up passkeys with Expo, there are a few additional steps to follow. [Learn more about passkeys for Expo](/docs/reference/expo/passkeys). </If>To create a passkey for a user, you must call User.createPasskey(), as shown in the following example:
export function CreatePasskeyButton() {
const { isSignedIn, user } = useUser()
const createClerkPasskey = async () => {
if (!isSignedIn) {
// Handle signed out state
return
}
try {
await user?.createPasskey()
} catch (err) {
// See https://clerk.com/docs/guides/development/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</button>
}
To sign a user into your Clerk app with a passkey, you must call SignIn.authenticateWithPasskey(). This method allows users to choose from their discoverable passkeys, such as hardware keys or passkeys in password managers.
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,
redirectUrl: '/',
navigate: async ({ session }) => {
if (session?.currentTask) {
// Check for tasks and navigate to custom UI to help users resolve them
// See https://clerk.com/docs/guides/development/custom-flows/overview#session-tasks
console.log(session?.currentTask)
return
}
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/guides/development/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>
}
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() method of the passkey object, as shown in the following example:
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/guides/development/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="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>
</>
)
}
To delete a user's passkey from your Clerk app, you must call the delete() method of the passkey object, as shown in the following example:
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/guides/development/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="Enter the passkey ID" />
<button onClick={deletePasskey}>Delete passkey</button>
<p>Passkey deleted: {success ? 'Yes' : 'No'}</p>
</>
)
}
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā