Skip to content

Commit 1ac0c8a

Browse files
committed
Adds Advanced: Client Hints
1 parent f4cd50b commit 1ac0c8a

5 files changed

Lines changed: 266 additions & 4 deletions

File tree

.cspell/custom-dictionary-workspace.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ UVRA
2525
webauthn
2626
webshare
2727
Yubico
28+
xmark

content/en/device-support/_index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ This matrix represents the default capabilities for a user out of the box. Addit
568568
</tr>
569569
<tr class="align-top">
570570
<td class="fw-bold">
571-
<a href="https://w3c.github.io/webauthn/#enum-hints" target="_blank">Client Hints</a>
571+
<a href="../docs/advanced/client-hints/" target="_blank">Client Hints</a>
572572
</td>
573573
<td class="text-center">
574574
{{< fas fa-circle-check fa-xl mb-2 text-success>}}
854 KB
Binary file not shown.
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
---
2+
title: "Client Hints"
3+
description: "The WebAuthn Client Hints feature allows a Relying Party to request a more specific credential manager or passkey selection experience from the WebAuthn client."
4+
date: 2025-09-24T05:07:38.473Z
5+
type: docs
6+
layout: docs
7+
---
8+
9+
## Overview
10+
11+
When creating a passkey, WebAuthn Clients display a credential manager selection screen asking users to choose where to store their new passkey. The selector typically defaults to local credential managers because they offer immediate availability and support for synced passkeys, the default credential type in unmanaged, consumer contexts.
12+
13+
During a sign in flow, the WebAuthn client will do its best to help the user select a passkey which is immediately available, and fall back to an external authenticator selection screen. This typically shows an option for [FIDO Cross-Device Authentication](../reference/terms/#cross-device-authentication-cda) and security keys. In environments where only security keys are allowed, having additional options can confuse users and lead to unnecessary steps.
14+
15+
The WebAuthn Client Hints feature allows a Relying Party to request a more predictable experience based on their requirements. It is important to note that this is only a hint, and is not used to enforce security policy.
16+
17+
## Use Cases and Usage
18+
19+
NOTE: Additional use cases will be added in the future.
20+
21+
### Security Keys
22+
23+
The primary use case for WebAuthn Client Hints is in workforce and high assurance consumer scenarios where creating passkeys on security keys is required by policy for all or a specific group of users. In these scenarios, if a user were to attempt to create a passkey on a different type of authenticator/credential manager (ex: using cross-device and saving to their personal credential manager on their phone), the Relying Party would reject the passkey, creating confusion for the user, and leaving them somewhat stranded.
24+
25+
There are three primary scenarios which will be referenced throughout this page:
26+
27+
1. Passkeys on security keys are required for all users in the organization
28+
2. Passkeys on security keys are required for a limited set of users in the organization (e.g. tied to a directory group)
29+
3. Passkeys on security keys are not required for any users but are encouraged
30+
31+
#### Passkey Creation
32+
33+
Below is a mapping of the scenarios from the beginning of this article and their solutions.
34+
35+
| **SCENARIO** | **SOLUTION** |
36+
|---------------------------|--------------------------------------------------------------------------------|
37+
| **1** (required for all) | Use Client Hints with your existing enrollment experience |
38+
| **2** (required for some) | Use Client Hints with your existing enrollment experience |
39+
| **3** (required for none) | Use Client Hints with a dedicated button |
40+
41+
##### Scenarios 1 & 2
42+
43+
In both scenarios 1 and 2, Client Hints is used to prefer the security key experience during passkey creation for all users.
44+
45+
Simply adapt your existing passkey creation flow to use the hints parameter as shown in the sample code below:
46+
47+
```html
48+
-- snip --
49+
50+
<button type="button" onclick="createPasskeyOnSecurityKey()">Create passkey</button>
51+
52+
-- snip --
53+
54+
<script>
55+
async function createPasskeyOnSecurityKey() {
56+
const credential = await navigator.credentials.create({
57+
publicKey: {
58+
challenge: "challenge-from-server",
59+
rp: { }, // omitted
60+
user: { }, // omitted
61+
pubKeyCredParams: [], // omitted
62+
timeout: 60000,
63+
hints: [ "security-key" ], // this is the WebAuthn Client Hints parameter
64+
authenticatorSelection: {
65+
residentKey: "required",
66+
userVerification: "preferred",
67+
authenticatorAttachment: "cross-platform"
68+
}
69+
}
70+
});
71+
// omitted
72+
};
73+
</script>
74+
```
75+
76+
{{< video src="hints-create-seckey.mp4" autoplay="true" loop="true" >}}
77+
78+
##### Scenario 3
79+
80+
For scenario 3, it is recommended to use dedicated buttons for "This device" vs "Security Key". This can help give the user a more predictable experience based on their selection.
81+
82+
Sample code:
83+
84+
```html
85+
-- snip --
86+
87+
<h1>Create a passkey!</h1>
88+
89+
<p>Would you like to create your passkey on this device or an external USB security key?</p>
90+
91+
<button type="button" onclick="createPasskeyOnLocalDevice()">This device</button>
92+
93+
<button type="button" onclick="createPasskeyOnSecurityKey()">USB Security Key</button>
94+
95+
-- snip --
96+
97+
<script>
98+
async function createPasskeyOnSecurityKey() {
99+
const credential = await navigator.credentials.create({
100+
publicKey: {
101+
challenge: "challenge-from-server",
102+
rp: { }, // omitted
103+
user: { }, // omitted
104+
pubKeyCredParams: [], // omitted
105+
timeout: 60000,
106+
hints: [ "security-key" ],
107+
authenticatorSelection: {
108+
residentKey: "required",
109+
userVerification: "preferred",
110+
authenticatorAttachment: "cross-platform"
111+
}
112+
}
113+
});
114+
// omitted
115+
};
116+
117+
async function createPasskeyOnLocalDevice() {
118+
const credential = await navigator.credentials.create({
119+
publicKey: {
120+
challenge: "challenge-from-server",
121+
rp: { }, // omitted
122+
user: { }, // omitted
123+
pubKeyCredParams: [], // omitted
124+
timeout: 60000,
125+
hints: [ "client-device" ],
126+
authenticatorSelection: {
127+
residentKey: "required",
128+
userVerification: "preferred",
129+
authenticatorAttachment: "platform"
130+
}
131+
}
132+
});
133+
// omitted
134+
};
135+
</script>
136+
```
137+
138+
#### Passkey Authentication
139+
140+
While Client Hints primarily targets passkey creation flows, it also supports specific authentication scenarios. Choose your approach based on your configuration and security requirements. Below is a mapping of the scenarios from the beginning of this article and their solutions.
141+
142+
| **SCENARIO** | **SOLUTION** |
143+
|---------------------------|--------------------------------------------------------------------------------|
144+
| **1** (required for all) | Use Client Hints |
145+
| **2** (required for some) | Use identifier-first plus an allow list |
146+
| **3** (required for none) | Don't use Client Hints or an allow list; let the WebAuthn Client do its thing |
147+
148+
##### Scenario 1
149+
150+
In scenario 1, Client Hints is used to prefer the security key experience for all users.
151+
152+
Sample code for scenario 1:
153+
154+
```html
155+
<!-- additional code omitted -->
156+
157+
<button type="button" onclick="signIn()">Sign in with a passkey</button>
158+
159+
<!-- additional code omitted -->
160+
161+
<script>
162+
async function signIn() {
163+
const credential = await navigator.credentials.get({
164+
publicKey: {
165+
challenge: "challenge-from-server",
166+
rpId: "", // omitted in example
167+
timeout: 60000,
168+
hints: [ "security-key" ]
169+
}
170+
});
171+
// additional code omitted
172+
};
173+
</script>
174+
```
175+
176+
##### Scenario 2
177+
178+
In scenario 2, an identifier-first flow is used where the user enters their username, and a request is made to the server for a list of credential IDs for the user. These are then passed in to the WebAuthn request (along with their transports) in the `allowCredentials` list. If only passkeys on security keys are included, the WebAuthn Client will show the security key experience.
179+
180+
Sample code:
181+
182+
```javascript
183+
// call this after the user has been identified
184+
// and the credential IDs have been looked up
185+
186+
async function signIn() {
187+
const credential = await navigator.credentials.get({
188+
publicKey: {
189+
challenge: "challenge-from-server",
190+
rpId: "", // omitted in example
191+
timeout: 60000,
192+
allowCredentials: [
193+
// the passkeys created on security keys
194+
// that are linked to the user's account
195+
{
196+
"id": "qx30Jbh0IJFq4Y3i7r5DY7aECNDnlH4-lldmDeshvTVFZolxwIgIBQfnoxrJKe1z",
197+
"type": "public-key",
198+
"transports": [ "nfc", "usb" ]
199+
},
200+
{
201+
"id": "3WiVDBng9vWlRX8Zkarc-4vpVNt8ysHFhHyYNldgf26n7eHJ4TN9AsBOr36Lsnl2",
202+
"type": "public-key",
203+
"transports": [ "usb" ]
204+
}
205+
]
206+
}
207+
});
208+
// additional code omitted
209+
};
210+
```
211+
212+
##### Scenario 3
213+
214+
In scenario 3, there is no special configuration, allowing the WebAuthn Client to do what it believes is best based on available context that only it has.
215+
216+
Sample code:
217+
218+
```javascript
219+
async function signIn() {
220+
const credential = await navigator.credentials.get({
221+
publicKey: {
222+
challenge: "challenge-from-server",
223+
rpId: "", // omitted in example
224+
timeout: 60000,
225+
}
226+
});
227+
// additional code omitted
228+
};
229+
```
230+
231+
## Client Hints vs Authenticator Attachment
232+
233+
> Coming Soon
234+
235+
## Additional Information
236+
237+
WebAuthn Client Hints support is rolling out across the ecosystem. Details about support WebAuthn Clients can be found in the [Device Support matrix](device-support#advanced).
238+
239+
A presentation by Tim Cappalli at Authenticate 2024 which dives into a WebAuthn Client's selection logic: ["Peeling back the passkeys onion"](https://blog.timcappalli.me/p/preso-authn24-passkeysonion/).
240+
241+
{{< button color="light" size="sm" icon="fas fa-circle-info" cue=false order="first" tooltip="Go to reference in the WebAuthn specification" href="https://www.w3.org/TR/webauthn-3/#enum-hints" >}}WebAuthn Spec Reference{{< /button >}}

hugo_stats.json

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"pre",
3535
"script",
3636
"small",
37+
"source",
3738
"span",
3839
"strong",
3940
"sup",
@@ -47,7 +48,8 @@
4748
"title",
4849
"tr",
4950
"ul",
50-
"use"
51+
"use",
52+
"video"
5153
],
5254
"classes": [
5355
"accordion",
@@ -70,6 +72,8 @@
7072
"blockquote-alert-note",
7173
"border",
7274
"border-0",
75+
"border-bottom",
76+
"border-none",
7377
"border-top",
7478
"bottom-0",
7579
"bottom-bar",
@@ -169,13 +173,13 @@
169173
"flex-fill",
170174
"flex-grow-1",
171175
"flex-shrink-0",
176+
"font-monospace",
172177
"footer",
173178
"footer-muted",
174179
"form-control",
175180
"fs-2",
176181
"fs-3",
177182
"fs-6",
178-
"fs-7",
179183
"fs-lg-5",
180184
"fs-md-5",
181185
"fst-italic",
@@ -192,6 +196,7 @@
192196
"heading",
193197
"highlight",
194198
"hstack",
199+
"html-video",
195200
"img-fluid",
196201
"invisible",
197202
"is-search",
@@ -257,6 +262,7 @@
257262
"pb-3",
258263
"pb-5",
259264
"pe-1",
265+
"pe-3",
260266
"position-fixed",
261267
"position-relative",
262268
"ps-0",
@@ -273,6 +279,8 @@
273279
"py-3",
274280
"py-5",
275281
"rounded",
282+
"rounded-bottom",
283+
"rounded-top",
276284
"row",
277285
"row-cols-1",
278286
"row-cols-2",
@@ -353,6 +361,7 @@
353361
"cda-client",
354362
"chrome-120",
355363
"chrome-120-with-icloud-keychain-on-macos-14",
364+
"client-hints-vs-authenticator-attachment",
356365
"client-support",
357366
"client-to-authenticator-protocol-ctap",
358367
"community-resources",
@@ -362,6 +371,7 @@
362371
"contributors",
363372
"copyright-and-attributions",
364373
"credential-exchange",
374+
"credential-manager",
365375
"cross-device-authentication",
366376
"cross-device-authentication-cda",
367377
"demo-sites",
@@ -449,6 +459,8 @@
449459
"other-fido2webauthn-libraries",
450460
"overview",
451461
"passkey",
462+
"passkey-authentication",
463+
"passkey-creation",
452464
"passkey-metadata",
453465
"passkey-provider",
454466
"passkeys-are",
@@ -476,6 +488,12 @@
476488
"safari-on-macos-14",
477489
"sample-code",
478490
"samsung-pass",
491+
"scenario-1",
492+
"scenario-2",
493+
"scenario-3",
494+
"scenario-3-1",
495+
"scenarios-1--2",
496+
"security-keys",
479497
"selection-criteria",
480498
"sensitive-actions",
481499
"sidebar-collapse-0-1",
@@ -499,6 +517,7 @@
499517
"typescript",
500518
"updated-for-passkeys",
501519
"use-cases",
520+
"use-cases-and-usage",
502521
"user-presence-up",
503522
"user-verification",
504523
"user-verification-behavior",
@@ -511,7 +530,8 @@
511530
"w3c-web-authentication-webauthn",
512531
"webauthn-versions-and-capabilities",
513532
"website-visitors",
514-
"webviews"
533+
"webviews",
534+
"zig"
515535
]
516536
}
517537
}

0 commit comments

Comments
 (0)