Skip to content

Commit d21061c

Browse files
committed
Update README and JavaDoc for passkeys
1 parent e952e68 commit d21061c

File tree

8 files changed

+214
-78
lines changed

8 files changed

+214
-78
lines changed

NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
== Version 2.4.1 (unreleased) ==
2+
3+
* Updated README and JavaDoc to use the "passkey" term and provide more guidance
4+
around passkey use cases.
5+
6+
17
== Version 2.4.1 ==
28

39
Changes:

README

Lines changed: 110 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ image:https://github.com/Yubico/java-webauthn-server/actions/workflows/release-v
1111
Server-side https://www.w3.org/TR/webauthn/[Web Authentication] library for
1212
Java. Provides implementations of the
1313
https://www.w3.org/TR/webauthn/#sctn-rp-operations[Relying Party operations] required
14-
for a server to support Web Authentication. This includes registering
15-
authenticators and authenticating registered authenticators.
14+
for a server to support Web Authentication, including https://passkeys.dev/[passkey authentication].
1615

1716

1817
[WARNING]
@@ -30,6 +29,7 @@ If you are, we urge you to upgrade your Java deployment to a version that is saf
3029

3130
*Table of contents*
3231

32+
:toclevels: 3
3333
toc::[]
3434

3535

@@ -133,7 +133,7 @@ The server side involves:
133133
and
134134
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html#finishAssertion(com.yubico.webauthn.FinishAssertionOptions)[`RelyingParty.finishAssertion(...)`]
135135
methods to perform authentication ceremonies.
136-
5. Use the outputs of `finishRegistration` and `finishAssertion` to update your database, initiate sessions, etc.
136+
5. Optionally use additional features: passkeys, passwordless multi-factor authentication, credential backup state.
137137

138138
The client side involves:
139139

@@ -275,18 +275,21 @@ Here is an example of things you will likely want to store:
275275

276276
[source,java]
277277
----------
278-
storeCredential( // Some database access method of your own design
279-
"alice", // Username or other appropriate user identifier
280-
result.getKeyId(), // Credential ID and transports for allowCredentials
281-
result.getPublicKeyCose(), // Public key for verifying authentication signatures
282-
result.isDiscoverable(), // Can this key be used for username-less auth?
283-
result.signatureCount(), // Initial signature counter value
278+
storeCredential( // Some database access method of your own design
279+
"alice", // Username or other appropriate user identifier
280+
result.getKeyId(), // Credential ID and transports for allowCredentials
281+
result.getPublicKeyCose(), // Public key for verifying authentication signatures
282+
result.getSignatureCount(), // Initial signature counter value
283+
result.isDiscoverable(), // Is this a passkey?
284+
result.isBackupEligible(), // Can this credential be backed up (synced)?
285+
result.isBackedUp(), // Is this credential currently backed up?
284286
pkc.getResponse().getAttestationObject(), // Store attestation object for future reference
285287
pkc.getResponse().getClientDataJSON() // Store client data for re-verifying signature if needed
286288
);
287289
----------
288290

289291

292+
[#getting-started-authentication]
290293
=== 4. Authentication
291294

292295
Like registration ceremonies, an authentication ceremony consists of 5 main steps:
@@ -367,31 +370,56 @@ throw new RuntimeException("Authentication failed");
367370

368371
Finally, if the previous step was successful, update your database using the
369372
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/AssertionResult.html[`AssertionResult`].
370-
Most importantly, you should update the signature counter. That might look something like this:
373+
Most importantly you should update the signature counter, and the backup state flag if you use it. That might look something like this:
371374

372375
[source,java]
373376
----------
374377
updateCredential( // Some database access method of your own design
375378
"alice", // Query by username or other appropriate user identifier
376379
result.getCredentialId(), // Query by credential ID of the credential used
377-
result.signatureCount(), // Set new signature counter value
380+
result.getSignatureCount(), // Set new signature counter value
381+
result.isBackedUp(), // Set new backup state flag
378382
Clock.systemUTC().instant() // Set time of last use (now)
379383
);
380384
----------
381385

382386
Then do whatever else you need - for example, initiate a user session.
383387

384388

385-
=== 5. Passwordless, username-less authentication
389+
=== 5. Optional features: passkeys, multi-factor, backup state
386390

387-
WebAuthn supports passwordless multi-factor authentication via on-authenticator
388-
https://www.w3.org/TR/webauthn-2/#user-verification[user verification],
389-
and username-less authentication via
390-
https://www.w3.org/TR/webauthn-2/#discoverable-credential[discoverable credentials]
391-
(sometimes the term "passwordless" is used to mean the combination of both, but
392-
here the two are treated separately).
391+
WebAuthn supports a number of additional features beyond the basics:
393392

394-
Discoverable credentials must be enabled at registration time by setting the
393+
- <<passkeys,Passkeys>>: passwordless, username-less authentication.
394+
link:https://passkey.org[Try it on passkey.org!]
395+
- <<user-verification,User verification>>: passwordless, streamlined multi-factor authentication.
396+
- <<autofill-ui,Autofill UI>>: Unintrusive passkey integration in traditional login forms.
397+
- <<credential-backup-state,Credential backup state>>: hints on how vulnerable the user is to authenticator loss.
398+
399+
400+
[#passkeys]
401+
==== Passkeys: passwordless, username-less authentication
402+
403+
A https://passkeys.dev/[passkey] is a WebAuthn credential that can simultaneously both _identify_ and _authenticate_ the user.
404+
This is also called a link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#discoverable-credential[discoverable credential].
405+
By default, credentials are created non-discoverable, which means the server
406+
must list them in the
407+
https://www.w3.org/TR/webauthn/#dom-publickeycredentialrequestoptions-allowcredentials[`allowCredentials`]
408+
parameter before the user can use them to authenticate.
409+
This is typically because the credential private key is not stored within the authenticator,
410+
but instead encoded into one of the credential IDs in `allowCredentials`.
411+
This way even a small hardware authenticator can have an unlimited credential capacity,
412+
but with the drawback that the user must first identify themself to the server
413+
so the server can retrieve the correct `allowCredentials` list.
414+
415+
Passkeys are instead stored within the authenticator, and also include the user's
416+
link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle[user handle]
417+
in addition to the credential ID.
418+
This way the user can be both identified and authenticated simultaneously.
419+
Many passkey-capable authenticators also offer a credential sync mechanism
420+
to allow one passkey to be used on multiple devices.
421+
422+
Passkeys can be created by setting the
395423
link:https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-authenticatorselection[`authenticatorSelection`].link:https://www.w3.org/TR/webauthn-2/#dom-authenticatorselectioncriteria-residentkey[`residentKey`]
396424
option:
397425

@@ -413,13 +441,22 @@ The username can then be omitted when starting an authentication ceremony:
413441
AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder().build());
414442
----------
415443

416-
Some authenticators might enable this feature even if not required, and setting
417-
the `residentKey` option to `ResidentKeyRequirement.PREFERRED` will enable it if the
418-
authenticator supports it. The
419-
https://www.w3.org/TR/webauthn-2/#sctn-authenticator-credential-properties-extension[`credProps` extension]
420-
can be used to determine whether the created credential is discoverable, and is enabled by default.
444+
Some authenticators might create passkeys even if not required, and setting
445+
the `residentKey` option to `ResidentKeyRequirement.PREFERRED` will create a passkey
446+
if the authenticator supports it.
447+
The
448+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegistrationResult.html#isDiscoverable()[`RegistrationResult.isDiscoverable()`]
449+
method can be used to determine whether the created credential is a passkey.
450+
This requires the
451+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/RegistrationExtensionInputs.RegistrationExtensionInputsBuilder.html#credProps()[`credProps` extension],
452+
to be enabled, which it is by default.
421453

422-
User verification can be enforced independently per authentication ceremony:
454+
455+
[#user-verification]
456+
==== User verification: passwordless multi-factor authentication
457+
458+
link:https://passkeys.dev/docs/reference/terms/#user-verification-uv[User verification]
459+
can be enforced independently per authentication ceremony:
423460

424461
[source,java]
425462
----------
@@ -448,6 +485,54 @@ PublicKeyCredentialCreationOptions request = rp.startRegistration(
448485
.build());
449486
----------
450487

488+
User verification can be used with both discoverable credentials (passkeys) and non-discoverable credentials.
489+
490+
491+
[#autofill-ui]
492+
==== Using passkeys with autofill UI
493+
494+
Passkeys on platform authenticators may also support the WebAuthn
495+
link:https://passkeys.dev/docs/reference/terms/#autofill-ui[autofill UI], also known as "conditional mediation".
496+
This can help onboard users who are unfamiliar with a fully username-less login flow,
497+
allowing a familiar username input field to opportunistically offer a shortcut using a passkey
498+
if the user has one on their device.
499+
500+
This library is compatible with the autofill UI but provides no server-side options for it,
501+
because the steps to enable it are taken on the front-end side.
502+
Using autofill UI does not affect the response verification procedure.
503+
504+
See the link:https://passkeys.dev/docs/use-cases/bootstrapping/[guide on passkeys.dev]
505+
for complete instructions on how to enable the autofill UI.
506+
In particular you need to:
507+
508+
- Add the credential request option `mediation: "conditional"`
509+
alongside the `publicKey` option generated by
510+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html#startAssertion(com.yubico.webauthn.StartAssertionOptions)[`RelyingParty.startAssertion(...)`],
511+
- Add `autocomplete="username webauthn"` to a username input field on the page, and
512+
- Call `navigator.credentials.get()` in the background.
513+
514+
If the Promise resolves, handle it like any other assertion response as described in
515+
<<getting-started-authentication>> above.
516+
517+
Because of technical limitations, autofill UI is as of May 2023 only supported for platform credentials,
518+
i.e., passkeys stored on the user's computing devices.
519+
Autofill UI might support passkeys on external security keys in the future.
520+
521+
522+
[#credential-backup-state]
523+
==== Credential backup state
524+
525+
Some authenticators may allow credentials to be backed up and/or synced between devices.
526+
This capability and its current state is signaled via the
527+
link:https://w3c.github.io/webauthn/#sctn-credential-backup[Credential Backup State] flags,
528+
which are available via the `isBackedUp()` and `isBackupEligible()` methods of
529+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegistrationResult.html[`RegistrationResult`]
530+
and
531+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/AssertionResult.html[`AssertionResult`].
532+
These can be used as a hint about how vulnerable a user is to authenticator loss.
533+
In particular, a user with only one credential which is not backed up
534+
may risk getting locked out if they lose their authenticator.
535+
451536

452537
== Migrating from version `1.x`
453538

webauthn-server-core/src/main/java/com/yubico/webauthn/AssertionRequest.java

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,10 @@ public class AssertionRequest {
5959
* <p>If both this and {@link #getUserHandle() userHandle} are empty, this indicates that this is
6060
* a request for an assertion by a <a
6161
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
62-
* credential</a>, and identification of the user has been deferred until the response is
63-
* received.
62+
* credential</a> (passkey), and identification of the user has been deferred until the response
63+
* is received.
64+
*
65+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
6466
*/
6567
private final String username;
6668

@@ -74,8 +76,10 @@ public class AssertionRequest {
7476
* <p>If both this and {@link #getUsername() username} are empty, this indicates that this is a
7577
* request for an assertion by a <a
7678
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
77-
* credential</a>, and identification of the user has been deferred until the response is
78-
* received.
79+
* credential</a> (passkey), and identification of the user has been deferred until the response
80+
* is received.
81+
*
82+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
7983
*/
8084
private final ByteArray userHandle;
8185

@@ -105,8 +109,10 @@ private AssertionRequest(
105109
* <p>If both this and {@link #getUserHandle()} are empty, this indicates that this is a request
106110
* for an assertion by a <a
107111
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
108-
* credential</a>, and identification of the user has been deferred until the response is
109-
* received.
112+
* credential</a> (passkey), and identification of the user has been deferred until the response
113+
* is received.
114+
*
115+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
110116
*/
111117
public Optional<String> getUsername() {
112118
return Optional.ofNullable(username);
@@ -121,8 +127,10 @@ public Optional<String> getUsername() {
121127
* <p>If both this and {@link #getUsername()} are empty, this indicates that this is a request for
122128
* an assertion by a <a
123129
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
124-
* credential</a>, and identification of the user has been deferred until the response is
125-
* received.
130+
* credential</a> (passkey), and identification of the user has been deferred until the response
131+
* is received.
132+
*
133+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
126134
*/
127135
public Optional<ByteArray> getUserHandle() {
128136
return Optional.ofNullable(userHandle);
@@ -215,8 +223,10 @@ public AssertionRequestBuilder publicKeyCredentialRequestOptions(
215223
*
216224
* <p>If this is empty, this indicates that this is a request for an assertion by a <a
217225
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
218-
* credential</a>, and identification of the user has been deferred until the response is
219-
* received.
226+
* credential</a> (passkey), and identification of the user has been deferred until the response
227+
* is received.
228+
*
229+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
220230
*/
221231
public AssertionRequestBuilder username(@NonNull Optional<String> username) {
222232
return this.username(username.orElse(null));
@@ -230,8 +240,10 @@ public AssertionRequestBuilder username(@NonNull Optional<String> username) {
230240
*
231241
* <p>If this is empty, this indicates that this is a request for an assertion by a <a
232242
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
233-
* credential</a>, and identification of the user has been deferred until the response is
234-
* received.
243+
* credential</a> (passkey), and identification of the user has been deferred until the response
244+
* is received.
245+
*
246+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
235247
*/
236248
public AssertionRequestBuilder username(String username) {
237249
this.username = username;
@@ -250,8 +262,10 @@ public AssertionRequestBuilder username(String username) {
250262
* <p>If both this and {@link #username(String)} are empty, this indicates that this is a
251263
* request for an assertion by a <a
252264
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
253-
* credential</a>, and identification of the user has been deferred until the response is
254-
* received.
265+
* credential</a> (passkey), and identification of the user has been deferred until the response
266+
* is received.
267+
*
268+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
255269
*/
256270
public AssertionRequestBuilder userHandle(@NonNull Optional<ByteArray> userHandle) {
257271
return this.userHandle(userHandle.orElse(null));
@@ -266,8 +280,10 @@ public AssertionRequestBuilder userHandle(@NonNull Optional<ByteArray> userHandl
266280
* <p>If both this and {@link #username(String)} are empty, this indicates that this is a
267281
* request for an assertion by a <a
268282
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#client-side-discoverable-public-key-credential-source">client-side-discoverable
269-
* credential</a>, and identification of the user has been deferred until the response is
270-
* received.
283+
* credential</a> (passkey), and identification of the user has been deferred until the response
284+
* is received.
285+
*
286+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
271287
*/
272288
public AssertionRequestBuilder userHandle(ByteArray userHandle) {
273289
if (userHandle != null) {

webauthn-server-core/src/main/java/com/yubico/webauthn/RegistrationResult.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -304,19 +304,21 @@ public Optional<AuthenticatorRegistrationExtensionOutputs> getAuthenticatorExten
304304
/**
305305
* Try to determine whether the created credential is a <a
306306
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#discoverable-credential">discoverable
307-
* credential</a>, using the output from the <a
307+
* credential</a>, also called a <i>passkey</i>, using the output from the <a
308308
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-authenticator-credential-properties-extension">
309309
* <code>credProps</code></a> extension.
310310
*
311-
* @return A present <code>true</code> if the created credential is discoverable. A present <code>
312-
* false</code> if the created credential is not discoverable. An empty value if it is not
313-
* known whether the created credential is discoverable.
311+
* @return A present <code>true</code> if the created credential is a passkey (discoverable). A
312+
* present <code>
313+
* false</code> if the created credential is not a passkey. An empty value if it is not known
314+
* whether the created credential is a passkey.
314315
* @see <a
315316
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#dom-credentialpropertiesoutput-rk">§10.4.
316317
* Credential Properties Extension (credProps), "rk" output</a>
317318
* @see <a
318319
* href="https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#discoverable-credential">Discoverable
319320
* Credential</a>
321+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a>
320322
*/
321323
@JsonIgnore
322324
public Optional<Boolean> isDiscoverable() {

0 commit comments

Comments
 (0)