Skip to content

Commit 1104940

Browse files
authored
Merge pull request #281 from Yubico/readme-passkey
Update README and JavaDoc for passkeys
2 parents 17b7bb3 + 0d6d412 commit 1104940

File tree

8 files changed

+243
-80
lines changed

8 files changed

+243
-80
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.2 (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: 117 additions & 27 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

@@ -44,7 +44,7 @@ toc::[]
4444
- Optionally integrates with an "attestation trust source" to verify
4545
https://www.w3.org/TR/webauthn/#sctn-attestation[authenticator attestations]
4646
- Reproducible builds: release signatures match fresh builds from source. See
47-
link:#reproducible-builds[Reproducible builds] below.
47+
<<reproducible-builds>> below.
4848

4949

5050
=== Non-features
@@ -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:
@@ -374,26 +377,52 @@ Most importantly, you should update the signature counter. That might look somet
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
395-
link:https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-authenticatorselection[`authenticatorSelection`].link:https://www.w3.org/TR/webauthn-2/#dom-authenticatorselectioncriteria-residentkey[`residentKey`]
396-
option:
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
423+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/StartRegistrationOptions.StartRegistrationOptionsBuilder.html#authenticatorSelection(com.yubico.webauthn.data.AuthenticatorSelectionCriteria)[`authenticatorSelection`].link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.AuthenticatorSelectionCriteriaBuilder.html#residentKey(com.yubico.webauthn.data.ResidentKeyRequirement)[`residentKey`]
424+
option to
425+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/ResidentKeyRequirement.html#REQUIRED[`REQUIRED`]:
397426

398427
[source,java]
399428
----------
@@ -413,13 +442,25 @@ The username can then be omitted when starting an authentication ceremony:
413442
AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder().build());
414443
----------
415444

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.
445+
Some authenticators might create passkeys even if not required, and setting
446+
the
447+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/AuthenticatorSelectionCriteria.AuthenticatorSelectionCriteriaBuilder.html#residentKey(com.yubico.webauthn.data.ResidentKeyRequirement)[`residentKey`]
448+
option to
449+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/ResidentKeyRequirement.html#PREFERRED[`PREFERRED`]
450+
will create a passkey if the authenticator supports it.
451+
The
452+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegistrationResult.html#isDiscoverable()[`RegistrationResult.isDiscoverable()`]
453+
method can be used to determine whether the created credential is a passkey.
454+
This requires the
455+
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]
456+
to be enabled, which it is by default.
421457

422-
User verification can be enforced independently per authentication ceremony:
458+
459+
[#user-verification]
460+
==== User verification: passwordless multi-factor authentication
461+
462+
link:https://passkeys.dev/docs/reference/terms/#user-verification-uv[User verification]
463+
can be enforced independently per authentication ceremony:
423464

424465
[source,java]
425466
----------
@@ -448,6 +489,54 @@ PublicKeyCredentialCreationOptions request = rp.startRegistration(
448489
.build());
449490
----------
450491

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

452541
== Migrating from version `1.x`
453542

@@ -689,6 +778,7 @@ $ ./gradlew pitest
689778
----------
690779

691780

781+
[#reproducible-builds]
692782
=== Reproducible builds
693783
Starting in version `1.4.0-RC2`, artifacts are built reproducibly. Fresh builds from
694784
tagged commits should therefore be verifiable by signatures from Maven Central

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

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,11 @@ 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). Identification of the user is therefore deferred until the response
63+
* is received.
64+
*
65+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
66+
* href="https://passkeys.dev">passkeys.dev</a> reference
6467
*/
6568
private final String username;
6669

@@ -74,8 +77,11 @@ public class AssertionRequest {
7477
* <p>If both this and {@link #getUsername() username} are empty, this indicates that this is a
7578
* request for an assertion by a <a
7679
* 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.
80+
* credential</a> (passkey). Identification of the user is therefore deferred until the response
81+
* is received.
82+
*
83+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
84+
* href="https://passkeys.dev">passkeys.dev</a> reference
7985
*/
8086
private final ByteArray userHandle;
8187

@@ -105,8 +111,11 @@ private AssertionRequest(
105111
* <p>If both this and {@link #getUserHandle()} are empty, this indicates that this is a request
106112
* for an assertion by a <a
107113
* 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.
114+
* credential</a> (passkey). Identification of the user is therefore deferred until the response
115+
* is received.
116+
*
117+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
118+
* href="https://passkeys.dev">passkeys.dev</a> reference
110119
*/
111120
public Optional<String> getUsername() {
112121
return Optional.ofNullable(username);
@@ -121,8 +130,11 @@ public Optional<String> getUsername() {
121130
* <p>If both this and {@link #getUsername()} are empty, this indicates that this is a request for
122131
* an assertion by a <a
123132
* 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.
133+
* credential</a> (passkey). Identification of the user is therefore deferred until the response
134+
* is received.
135+
*
136+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
137+
* href="https://passkeys.dev">passkeys.dev</a> reference
126138
*/
127139
public Optional<ByteArray> getUserHandle() {
128140
return Optional.ofNullable(userHandle);
@@ -215,8 +227,11 @@ public AssertionRequestBuilder publicKeyCredentialRequestOptions(
215227
*
216228
* <p>If this is empty, this indicates that this is a request for an assertion by a <a
217229
* 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.
230+
* credential</a> (passkey). Identification of the user is therefore deferred until the response
231+
* is received.
232+
*
233+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
234+
* href="https://passkeys.dev">passkeys.dev</a> reference
220235
*/
221236
public AssertionRequestBuilder username(@NonNull Optional<String> username) {
222237
return this.username(username.orElse(null));
@@ -230,8 +245,11 @@ public AssertionRequestBuilder username(@NonNull Optional<String> username) {
230245
*
231246
* <p>If this is empty, this indicates that this is a request for an assertion by a <a
232247
* 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.
248+
* credential</a> (passkey). Identification of the user is therefore deferred until the response
249+
* is received.
250+
*
251+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
252+
* href="https://passkeys.dev">passkeys.dev</a> reference
235253
*/
236254
public AssertionRequestBuilder username(String username) {
237255
this.username = username;
@@ -250,8 +268,11 @@ public AssertionRequestBuilder username(String username) {
250268
* <p>If both this and {@link #username(String)} are empty, this indicates that this is a
251269
* request for an assertion by a <a
252270
* 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.
271+
* credential</a> (passkey). Identification of the user is therefore deferred until the response
272+
* is received.
273+
*
274+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
275+
* href="https://passkeys.dev">passkeys.dev</a> reference
255276
*/
256277
public AssertionRequestBuilder userHandle(@NonNull Optional<ByteArray> userHandle) {
257278
return this.userHandle(userHandle.orElse(null));
@@ -266,8 +287,11 @@ public AssertionRequestBuilder userHandle(@NonNull Optional<ByteArray> userHandl
266287
* <p>If both this and {@link #username(String)} are empty, this indicates that this is a
267288
* request for an assertion by a <a
268289
* 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.
290+
* credential</a> (passkey). Identification of the user is therefore deferred until the response
291+
* is received.
292+
*
293+
* @see <a href="https://passkeys.dev/docs/reference/terms/#passkey">Passkey</a> in <a
294+
* href="https://passkeys.dev">passkeys.dev</a> reference
271295
*/
272296
public AssertionRequestBuilder userHandle(ByteArray userHandle) {
273297
if (userHandle != null) {

0 commit comments

Comments
 (0)