Skip to content

Commit 83a13bf

Browse files
AlexNtialexisintechnikospapcom
authored
feat: Add expo passkeys guide (#1702)
Co-authored-by: Alexis Aguilar <[email protected]> Co-authored-by: nikospapcom <[email protected]>
1 parent c15b149 commit 83a13bf

File tree

4 files changed

+337
-155
lines changed

4 files changed

+337
-155
lines changed

docs/authentication/configuration/sign-up-sign-in-options.mdx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ A passkey is a type of sign-in credential that requires one user action, but use
6565
1. A pin number or biometric data
6666
1. A physical device
6767

68-
Passkeys are the most secure **passwordless** strategy because they use two factors.
69-
7068
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.
7169

7270
#### Manage user passkeys

docs/custom-flows/passkeys.mdx

Lines changed: 130 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ description: Learn how to use the Clerk API to build a custom authentication flo
55

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

8-
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.
8+
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.
99

10-
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.
10+
This guide demonstrates how to use the Clerk API to build a custom user interface for creating, signing users in with, and managing passkeys.
1111

1212
## Enable passkeys
1313

@@ -24,182 +24,159 @@ To use passkeys, first enable the strategy in the Clerk Dashboard.
2424

2525
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:
2626

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

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

33-
export function CreatePasskeyButton() {
34-
const { user } = useUser()
35-
36-
const createClerkPasskey = async () => {
37-
try {
38-
const response = await user?.createPasskey()
39-
console.log(response)
40-
} catch (err) {
41-
// See https://clerk.com/docs/custom-flows/error-handling
42-
// for more info on error handling
43-
console.error('Error:', JSON.stringify(err, null, 2))
44-
}
34+
try {
35+
await user?.createPasskey()
36+
} catch (err) {
37+
// See https://clerk.com/docs/custom-flows/error-handling
38+
// for more info on error handling
39+
console.error('Error:', JSON.stringify(err, null, 2))
4540
}
46-
47-
return <button onClick={createClerkPasskey}>Create a passkey now</button>
4841
}
49-
```
50-
</CodeBlockTabs>
42+
43+
return <button onClick={createClerkPasskey}>Create a passkey</button>
44+
}
45+
```
5146

5247
## Sign a user in with a passkey
5348

5449
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.
5550

56-
<CodeBlockTabs options={["Next.js"]}>
57-
```tsx {{ filename: 'components/SignInWithPasskeyButton.tsx' }}
58-
'use client'
59-
60-
import { useSignIn } from '@clerk/nextjs'
61-
import { useRouter } from 'next/navigation'
62-
63-
export function SignInWithPasskeyButton() {
64-
const { signIn } = useSignIn()
65-
const router = useRouter()
66-
67-
const signInWithPasskey = async () => {
68-
// 'discoverable' lets the user choose a passkey
69-
// without autofilling any of the options
70-
try {
71-
const signInAttempt = await signIn?.authenticateWithPasskey({
72-
flow: 'discoverable',
73-
})
74-
75-
if (signInAttempt?.status === 'complete') {
76-
router.push('/')
77-
} else {
78-
// If the status is not complete, check why. User may need to
79-
// complete further steps.
80-
console.error(JSON.stringify(signInAttempt, null, 2))
81-
}
82-
} catch (err) {
83-
// See https://clerk.com/docs/custom-flows/error-handling
84-
// for more info on error handling
85-
console.error('Error:', JSON.stringify(err, null, 2))
51+
```tsx {{ filename: 'components/SignInWithPasskeyButton.tsx' }}
52+
export function SignInWithPasskeyButton() {
53+
const { signIn } = useSignIn()
54+
const router = useRouter()
55+
56+
const signInWithPasskey = async () => {
57+
// 'discoverable' lets the user choose a passkey
58+
// without auto-filling any of the options
59+
try {
60+
const signInAttempt = await signIn?.authenticateWithPasskey({
61+
flow: 'discoverable',
62+
})
63+
64+
if (signInAttempt?.status === 'complete') {
65+
await setActive({ session: signInAttempt.createdSessionId })
66+
router.push('/')
67+
} else {
68+
// If the status is not complete, check why. User may need to
69+
// complete further steps.
70+
console.error(JSON.stringify(signInAttempt, null, 2))
8671
}
72+
} catch (err) {
73+
// See https://clerk.com/docs/custom-flows/error-handling
74+
// for more info on error handling
75+
console.error('Error:', JSON.stringify(err, null, 2))
8776
}
88-
89-
return <button onClick={signInWithPasskey}>Sign in with a passkey</button>
9077
}
91-
```
92-
</CodeBlockTabs>
78+
79+
return <button onClick={signInWithPasskey}>Sign in with a passkey</button>
80+
}
81+
```
9382

9483
## Rename user passkeys
9584

9685
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.
9786

9887
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:
9988

100-
<CodeBlockTabs options={["Next.js"]}>
101-
```tsx {{ filename: 'components/RenamePasskeyUI.tsx' }}
102-
'use client'
103-
104-
import { useUser } from '@clerk/nextjs'
105-
import { PasskeyResource } from '@clerk/types'
106-
import { useRef, useState } from 'react'
107-
108-
export function RenamePasskeyUI() {
109-
const { user } = useUser()
110-
const passkeyToUpdateId = useRef<HTMLInputElement>(null)
111-
const newPasskeyName = useRef<HTMLInputElement>(null)
112-
const { passkeys } = user
113-
const [success, setSuccess] = useState(false)
114-
115-
const renamePasskey = async () => {
116-
try {
117-
const passkeyToUpdate = passkeys?.find(
118-
(pk: PasskeyResource) => pk.id === passkeyToUpdateId.current?.value,
119-
)
120-
const response = await passkeyToUpdate?.update({
121-
name: newPasskeyName.current?.value,
122-
})
123-
console.log(response)
124-
setSuccess(true)
125-
} catch (err) {
126-
// See https://clerk.com/docs/custom-flows/error-handling
127-
// for more info on error handling
128-
console.error('Error:', JSON.stringify(err, null, 2))
129-
setSuccess(false)
130-
}
89+
```tsx {{ filename: 'components/RenamePasskeyUI.tsx' }}
90+
export function RenamePasskeyUI() {
91+
const { user } = useUser()
92+
const { passkeys } = user
93+
94+
const passkeyToUpdateId = useRef<HTMLInputElement>(null)
95+
const newPasskeyName = useRef<HTMLInputElement>(null)
96+
const [success, setSuccess] = useState(false)
97+
98+
const renamePasskey = async () => {
99+
try {
100+
const passkeyToUpdate = passkeys?.find(
101+
(pk: PasskeyResource) => pk.id === passkeyToUpdateId.current?.value,
102+
)
103+
104+
await passkeyToUpdate?.update({
105+
name: newPasskeyName.current?.value,
106+
})
107+
108+
setSuccess(true)
109+
} catch (err) {
110+
// See https://clerk.com/docs/custom-flows/error-handling
111+
// for more info on error handling
112+
console.error('Error:', JSON.stringify(err, null, 2))
113+
setSuccess(false)
131114
}
132-
133-
return (
134-
<>
135-
<p>Passkeys:</p>
136-
<ul>
137-
{passkeys?.map((pk: PasskeyResource) => {
138-
return (
139-
<li key={pk.id}>
140-
Name: {pk.name} | ID: {pk.id}
141-
</li>
142-
)
143-
})}
144-
</ul>
145-
<input ref={passkeyToUpdateId} type="text" placeholder="ID of passkey to update" />
146-
<input type="text" placeholder="New name" ref={newPasskeyName} />
147-
<button onClick={renamePasskey}>Rename your passkey</button>
148-
<p>Passkey updated: {success ? 'Yes' : 'No'}</p>
149-
</>
150-
)
151115
}
152-
```
153-
</CodeBlockTabs>
116+
117+
return (
118+
<>
119+
<p>Passkeys:</p>
120+
<ul>
121+
{passkeys?.map((pk: PasskeyResource) => {
122+
return (
123+
<li key={pk.id}>
124+
Name: {pk.name} | ID: {pk.id}
125+
</li>
126+
)
127+
})}
128+
</ul>
129+
<input ref={passkeyToUpdateId} type="text" placeholder="Enter the passkey ID" />
130+
<input type="text" placeholder="Enter the passkey's new name" ref={newPasskeyName} />
131+
<button onClick={renamePasskey}>Rename passkey</button>
132+
<p>Passkey updated: {success ? 'Yes' : 'No'}</p>
133+
</>
134+
)
135+
}
136+
```
154137

155138
## Delete user passkeys
156139

157140
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:
158141

159-
<CodeBlockTabs options={["Next.js"]}>
160-
```tsx {{ filename: 'components/DeletePasskeyUI.tsx' }}
161-
'use client'
162-
163-
import { useUser } from '@clerk/nextjs'
164-
import { useRef, useState } from 'react'
165-
166-
export function DeletePasskeyUI() {
167-
const { user } = useUser()
168-
const passkeyToDeleteId = useRef<HTMLInputElement>(null)
169-
const { passkeys } = user
170-
const [success, setSuccess] = useState(false)
171-
172-
const deletePasskey = async () => {
173-
const passkeyToDelete = passkeys?.find((pk: any) => pk.id === passkeyToDeleteId.current?.value)
174-
try {
175-
const response = await passkeyToDelete?.delete()
176-
console.log(response)
177-
setSuccess(true)
178-
} catch (err) {
179-
// See https://clerk.com/docs/custom-flows/error-handling
180-
// for more info on error handling
181-
console.error('Error:', JSON.stringify(err, null, 2))
182-
setSuccess(false)
183-
}
142+
```tsx {{ filename: 'components/DeletePasskeyUI.tsx' }}
143+
export function DeletePasskeyUI() {
144+
const { user } = useUser()
145+
const { passkeys } = user
146+
147+
const passkeyToDeleteId = useRef<HTMLInputElement>(null)
148+
const [success, setSuccess] = useState(false)
149+
150+
const deletePasskey = async () => {
151+
const passkeyToDelete = passkeys?.find((pk: any) => pk.id === passkeyToDeleteId.current?.value)
152+
try {
153+
await passkeyToDelete?.delete()
154+
155+
setSuccess(true)
156+
} catch (err) {
157+
// See https://clerk.com/docs/custom-flows/error-handling
158+
// for more info on error handling
159+
console.error('Error:', JSON.stringify(err, null, 2))
160+
setSuccess(false)
184161
}
185-
186-
return (
187-
<>
188-
<p>Passkeys:</p>
189-
<ul>
190-
{passkeys?.map((pk: any) => {
191-
return (
192-
<li key={pk.id}>
193-
Name: {pk.name} | ID: {pk.id}
194-
</li>
195-
)
196-
})}
197-
</ul>
198-
<input ref={passkeyToDeleteId} type="text" placeholder="ID of passkey to delete" />
199-
<button onClick={deletePasskey}>Delete passkey</button>
200-
<p>Passkey deleted: {success ? 'Yes' : 'No'}</p>
201-
</>
202-
)
203162
}
204-
```
205-
</CodeBlockTabs>
163+
164+
return (
165+
<>
166+
<p>Passkeys:</p>
167+
<ul>
168+
{passkeys?.map((pk: any) => {
169+
return (
170+
<li key={pk.id}>
171+
Name: {pk.name} | ID: {pk.id}
172+
</li>
173+
)
174+
})}
175+
</ul>
176+
<input ref={passkeyToDeleteId} type="text" placeholder="Enter the passkey ID" />
177+
<button onClick={deletePasskey}>Delete passkey</button>
178+
<p>Passkey deleted: {success ? 'Yes' : 'No'}</p>
179+
</>
180+
)
181+
}
182+
```

docs/manifest.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,10 @@
803803
"title": "Use biometrics with local credentials",
804804
"href": "/docs/references/expo/local-credentials"
805805
},
806+
{
807+
"title": "Configure passkeys",
808+
"href": "/docs/references/expo/passkeys"
809+
},
806810
{
807811
"title": "Offline support",
808812
"href": "/docs/references/expo/offline-support"

0 commit comments

Comments
 (0)