forked from Yubico/java-webauthn-server
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README
807 lines (625 loc) · 40.9 KB
/
README
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
java-webauthn-server
====================
:toc:
:toc-placement: macro
:toc-title:
image:https://github.com/Yubico/java-webauthn-server/workflows/build/badge.svg["Build Status", link="https://github.com/Yubico/java-webauthn-server/actions"]
image:https://img.shields.io/endpoint?url=https%3A%2F%2FYubico.github.io%2Fjava-webauthn-server%2Fcoverage-badge.json["Mutation test coverage", link="https://Yubico.github.io/java-webauthn-server/"]
image:https://github.com/Yubico/java-webauthn-server/actions/workflows/release-verify-signatures.yml/badge.svg["Binary reproducibility", link="https://github.com/Yubico/java-webauthn-server/actions/workflows/release-verify-signatures.yml"]
Server-side https://www.w3.org/TR/webauthn/[Web Authentication] library for
Java. Provides implementations of the
https://www.w3.org/TR/webauthn/#sctn-rp-operations[Relying Party operations] required
for a server to support Web Authentication, including https://passkeys.dev/[passkey authentication].
[WARNING]
.*Psychic signatures in Java*
==========
In April 2022, link:https://neilmadden.blog/2022/04/19/psychic-signatures-in-java/[CVE-2022-21449]
was disclosed in Oracle's OpenJDK (and other JVMs derived from it) which can impact applications using java-webauthn-server.
The impact is that for the most common type of WebAuthn credential, invalid signatures are accepted as valid,
allowing authentication bypass for users with such a credential.
Please read link:https://openjdk.java.net/groups/vulnerability/advisories/2022-04-19[Oracle's advisory]
and make sure you are not using one of the impacted OpenJDK versions.
If you are, we urge you to upgrade your Java deployment to a version that is safe.
==========
*Table of contents*
:toclevels: 3
toc::[]
== Features
- Generates request objects suitable as parameters to
`navigator.credentials.create()` and `.get()`
- Performs all necessary
https://www.w3.org/TR/webauthn/#sctn-rp-operations[validation logic] on the
response from the client
- No mutable state or side effects - everything (except builders) is thread safe
- Optionally integrates with an "attestation trust source" to verify
https://www.w3.org/TR/webauthn/#sctn-attestation[authenticator attestations]
- Reproducible builds: release signatures match fresh builds from source. See
<<reproducible-builds>> below.
=== Non-features
This library has no concept of accounts, sessions, permissions or identity
federation, and it is not an authentication framework; it only deals with
executing the WebAuthn authentication mechanism. Sessions, account management
and other higher level concepts can make use of this authentication mechanism,
but the authentication mechanism alone does not make a security system.
== Dependency configuration
Maven:
----------
<dependency>
<groupId>com.yubico</groupId>
<artifactId>webauthn-server-core</artifactId>
<version>2.4.1</version>
<scope>compile</scope>
</dependency>
----------
Gradle:
----------
implementation("com.yubico:webauthn-server-core:2.4.1")
----------
NOTE: You may need additional dependencies with JCA providers to support some signature algorithms.
In particular, OpenJDK 14 and earlier does not include providers for the EdDSA family of algorithms.
The library will log warnings if you try to configure it for algorithms with no JCA provider available.
=== Semantic versioning
This library uses link:https://semver.org/[semantic versioning].
The public API consists of all public classes, methods and fields in the `com.yubico.webauthn` package and its subpackages,
i.e., everything covered by the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/package-summary.html[Javadoc],
*with the exception* of features annotated with a `@Deprecated` annotation and a
`@deprecated EXPERIMENTAL:` tag in JavaDoc.
Such features are considered unstable and may receive breaking changes without a
major version increase.
Package-private classes and methods are NOT part of the public API.
The `com.yubico:yubico-util` module is NOT part of the public API.
Breaking changes to these will NOT be reflected in version numbers.
=== Additional modules
In addition to the main `webauthn-server-core` module, there is also:
- link:webauthn-server-attestation[`webauthn-server-attestation`]: Integration with the https://fidoalliance.org/metadata/[FIDO Metadata Service]
for retrieving and selecting trust roots to use for verifying
https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation[attestation statements].
== Documentation
See the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/package-summary.html[Javadoc]
for in-depth API documentation.
== Getting started
Using this library comes in two parts: the server side and the client side.
The server side involves:
1. Implement the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
interface with your database access logic.
2. Instantiate the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
class.
3. Use the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html#startRegistration(com.yubico.webauthn.StartRegistrationOptions)[`RelyingParty.startRegistration(...)`]
and
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.finishRegistration(...)`]
methods to perform registration ceremonies.
4. Use the
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(...)`]
and
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(...)`]
methods to perform authentication ceremonies.
5. Optionally use additional features: passkeys, passwordless multi-factor authentication, credential backup state.
The client side involves:
1. Call `navigator.credentials.create()` or `.get()`,
passing the result from `RelyingParty.startRegistration(...)` or `.startAssertion(...)` as the argument.
2. Encode the result of the successfully resolved promise and return it to the server.
For this you need some way to encode `Uint8Array` values;
this guide will use GitHub's link:https://github.com/github/webauthn-json[webauthn-json] library.
Example code is given below.
For more detailed example usage, see
link:webauthn-server-demo[`webauthn-server-demo`] for a complete demo server.
=== 1. Implement a `CredentialRepository`
The
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
interface abstracts your database in a database-agnostic way.
The concrete implementation will be different for every project, but you can use
link:https://github.com/Yubico/java-webauthn-server/blob/main/webauthn-server-demo/src/main/java/demo/webauthn/InMemoryRegistrationStorage.java[`InMemoryRegistrationStorage`]
as a simple example.
=== 2. Instantiate a `RelyingParty`
The
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
class is the main entry point to the library.
You can instantiate it using its builder methods,
passing in your
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
implementation (called `MyCredentialRepository` here) as an argument:
[source,java]
----------
RelyingPartyIdentity rpIdentity = RelyingPartyIdentity.builder()
.id("example.com") // Set this to a parent domain that covers all subdomains
// where users' credentials should be valid
.name("Example Application")
.build();
RelyingParty rp = RelyingParty.builder()
.identity(rpIdentity)
.credentialRepository(new MyCredentialRepository())
.build();
----------
=== 3. Registration
A registration ceremony consists of 5 main steps:
1. Generate registration parameters using
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html#startRegistration(com.yubico.webauthn.StartRegistrationOptions)[`RelyingParty.startRegistration(...)`].
2. Send registration parameters to the client and call
https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create[`navigator.credentials.create()`].
3. With `cred` as the result of the successfully resolved promise,
call https://www.w3.org/TR/webauthn-2/#ref-for-dom-publickeycredential-getclientextensionresults[`cred.getClientExtensionResults()`]
and https://www.w3.org/TR/webauthn-2/#ref-for-dom-authenticatorattestationresponse-gettransports[`cred.response.getTransports()`]
and return their results along with `cred` to the server.
4. Validate the response using
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.finishRegistration(...)`].
5. Update your database using the `finishRegistration` output.
This example uses GitHub's link:https://github.com/github/webauthn-json[webauthn-json] library to do both (2) and (3) in one function call.
First, generate registration parameters and send them to the client:
[source,java]
----------
Optional<UserIdentity> findExistingUser(String username) { /* ... */ }
PublicKeyCredentialCreationOptions request = rp.startRegistration(
StartRegistrationOptions.builder()
.user(
findExistingUser("alice")
.orElseGet(() -> {
byte[] userHandle = new byte[64];
random.nextBytes(userHandle);
return UserIdentity.builder()
.name("alice")
.displayName("Alice Hypothetical")
.id(new ByteArray(userHandle))
.build();
})
)
.build());
String credentialCreateJson = request.toCredentialsCreateJson();
return credentialCreateJson; // Send to client
----------
You will need to keep this
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html[`PublicKeyCredentialCreationOptions`]
object in temporary storage
so you can also pass it into
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html#finishRegistration(com.yubico.webauthn.FinishRegistrationOptions)[`RelyingParty.finishRegistration(...)`]
later.
If needed, you can use the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html#toJson()[`toJson()`]
and
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/PublicKeyCredentialCreationOptions.html#fromJson(java.lang.String)[`fromJson(String)`]
methods to serialize and deserialize the value for storage.
Now call the WebAuthn API on the client side:
[source,javascript]
----------
import * as webauthnJson from "@github/webauthn-json";
// Make the call that returns the credentialCreateJson above
const credentialCreateOptions = await fetch(/* ... */).then(resp => resp.json());
// Call WebAuthn ceremony using webauthn-json wrapper
const publicKeyCredential = await webauthnJson.create(credentialCreateOptions);
// Return encoded PublicKeyCredential to server
fetch(/* ... */, { body: JSON.stringify(publicKeyCredential) });
----------
Validate the response on the server side:
[source,java]
----------
String publicKeyCredentialJson = /* ... */; // publicKeyCredential from client
PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> pkc =
PublicKeyCredential.parseRegistrationResponseJson(publicKeyCredentialJson);
try {
RegistrationResult result = rp.finishRegistration(FinishRegistrationOptions.builder()
.request(request) // The PublicKeyCredentialCreationOptions from startRegistration above
// NOTE: Must be stored in server memory or otherwise protected against tampering
.response(pkc)
.build());
} catch (RegistrationFailedException e) { /* ... */ }
----------
Finally, if the previous step was successful, store the new credential in your database.
Here is an example of things you will likely want to store:
[source,java]
----------
storeCredential( // Some database access method of your own design
"alice", // Username or other appropriate user identifier
result.getKeyId(), // Credential ID and transports for allowCredentials
result.getPublicKeyCose(), // Public key for verifying authentication signatures
result.getSignatureCount(), // Initial signature counter value
result.isDiscoverable(), // Is this a passkey?
result.isBackupEligible(), // Can this credential be backed up (synced)?
result.isBackedUp(), // Is this credential currently backed up?
pkc.getResponse().getAttestationObject(), // Store attestation object for future reference
pkc.getResponse().getClientDataJSON() // Store client data for re-verifying signature if needed
);
----------
[#getting-started-authentication]
=== 4. Authentication
Like registration ceremonies, an authentication ceremony consists of 5 main steps:
1. Generate authentication parameters using
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(...)`].
2. Send authentication parameters to the client, call
https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/get[`navigator.credentials.get()`]
and return the response.
3. With `cred` as the result of the successfully resolved promise, call
https://www.w3.org/TR/webauthn-2/#ref-for-dom-publickeycredential-getclientextensionresults[`cred.getClientExtensionResults()`]
and return the result along with `cred` to the server.
4. Validate the response using
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(...)`].
5. Update your database using the `finishAssertion` output, and act upon the result (for example, grant login access).
This example uses GitHub's link:https://github.com/github/webauthn-json[webauthn-json] library to do both (2) and (3) in one function call.
First, generate authentication parameters and send them to the client:
[source,java]
----------
AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder()
.username("alice") // Or .userHandle(ByteArray) if preferred
.build());
String credentialGetJson = request.toCredentialsGetJson();
return credentialGetJson; // Send to client
----------
Again, you will need to keep this
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/AssertionRequest.html[`AssertionRequest`]
object in temporary storage
so you can also pass it into
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(...)`]
later.
If needed, you can use the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/AssertionRequest.html#toJson()[`toJson()`]
and
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/AssertionRequest.html#fromJson(java.lang.String)[`fromJson(String)`]
methods to serialize and deserialize the value for storage.
Now call the WebAuthn API on the client side:
[source,javascript]
----------
import * as webauthnJson from "@github/webauthn-json";
// Make the call that returns the credentialGetJson above
const credentialGetOptions = await fetch(/* ... */).then(resp => resp.json());
// Call WebAuthn ceremony using webauthn-json wrapper
const publicKeyCredential = await webauthnJson.get(credentialGetOptions);
// Return encoded PublicKeyCredential to server
fetch(/* ... */, { body: JSON.stringify(publicKeyCredential) });
----------
Validate the response on the server side:
[source,java]
----------
String publicKeyCredentialJson = /* ... */; // publicKeyCredential from client
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc =
PublicKeyCredential.parseAssertionResponseJson(publicKeyCredentialJson);
try {
AssertionResult result = rp.finishAssertion(FinishAssertionOptions.builder()
.request(request) // The PublicKeyCredentialRequestOptions from startAssertion above
.response(pkc)
.build());
if (result.isSuccess()) {
return result.getUsername();
}
} catch (AssertionFailedException e) { /* ... */ }
throw new RuntimeException("Authentication failed");
----------
Finally, if the previous step was successful, update your database using the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/AssertionResult.html[`AssertionResult`].
Most importantly, you should update the signature counter. That might look something like this:
[source,java]
----------
updateCredential( // Some database access method of your own design
"alice", // Query by username or other appropriate user identifier
result.getCredentialId(), // Query by credential ID of the credential used
result.getSignatureCount(), // Set new signature counter value
result.isBackedUp(), // Set new backup state flag
Clock.systemUTC().instant() // Set time of last use (now)
);
----------
Then do whatever else you need - for example, initiate a user session.
=== 5. Optional features: passkeys, multi-factor, backup state
WebAuthn supports a number of additional features beyond the basics:
- <<passkeys,Passkeys>>: passwordless, username-less authentication.
link:https://passkey.org[Try it on passkey.org!]
- <<user-verification,User verification>>: passwordless, streamlined multi-factor authentication.
- <<autofill-ui,Autofill UI>>: Unintrusive passkey integration in traditional login forms.
- <<credential-backup-state,Credential backup state>>: hints on how vulnerable the user is to authenticator loss.
[#passkeys]
==== Passkeys: passwordless, username-less authentication
A https://passkeys.dev/[passkey] is a WebAuthn credential that can simultaneously both _identify_ and _authenticate_ the user.
This is also called a link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#discoverable-credential[discoverable credential].
By default, credentials are created non-discoverable, which means the server
must list them in the
https://www.w3.org/TR/webauthn/#dom-publickeycredentialrequestoptions-allowcredentials[`allowCredentials`]
parameter before the user can use them to authenticate.
This is typically because the credential private key is not stored within the authenticator,
but instead encoded into one of the credential IDs in `allowCredentials`.
This way even a small hardware authenticator can have an unlimited credential capacity,
but with the drawback that the user must first identify themself to the server
so the server can retrieve the correct `allowCredentials` list.
Passkeys are instead stored within the authenticator, and also include the user's
link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle[user handle]
in addition to the credential ID.
This way the user can be both identified and authenticated simultaneously.
Many passkey-capable authenticators also offer a credential sync mechanism
to allow one passkey to be used on multiple devices.
Passkeys can be created by setting the
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`]
option to
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/ResidentKeyRequirement.html#REQUIRED[`REQUIRED`]:
[source,java]
----------
PublicKeyCredentialCreationOptions request = rp.startRegistration(
StartRegistrationOptions.builder()
.user(/* ... */)
.authenticatorSelection(AuthenticatorSelectionCriteria.builder()
.residentKey(ResidentKeyRequirement.REQUIRED)
.build())
.build());
----------
The username can then be omitted when starting an authentication ceremony:
[source,java]
----------
AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder().build());
----------
Some authenticators might create passkeys even if not required, and setting
the
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`]
option to
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/ResidentKeyRequirement.html#PREFERRED[`PREFERRED`]
will create a passkey if the authenticator supports it.
The
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegistrationResult.html#isDiscoverable()[`RegistrationResult.isDiscoverable()`]
method can be used to determine whether the created credential is a passkey.
This requires the
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]
to be enabled, which it is by default.
[#user-verification]
==== User verification: passwordless multi-factor authentication
link:https://passkeys.dev/docs/reference/terms/#user-verification-uv[User verification]
can be enforced independently per authentication ceremony:
[source,java]
----------
AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder()
.username("alice")
.userVerification(UserVerificationRequirement.REQUIRED)
.build());
----------
Then
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(...)`]
will enforce that user verification was performed.
However, there is no guarantee that the user's authenticator will support this
unless the user has some credential created with the
link:https://www.w3.org/TR/webauthn-2/#dom-publickeycredentialcreationoptions-authenticatorselection[`authenticatorSelection`].link:https://www.w3.org/TR/webauthn-2/#dom-authenticatorselectioncriteria-userverification[`userVerification`]
option set:
[source,java]
----------
PublicKeyCredentialCreationOptions request = rp.startRegistration(
StartRegistrationOptions.builder()
.user(/* ... */)
.authenticatorSelection(AuthenticatorSelectionCriteria.builder()
.userVerification(UserVerificationRequirement.REQUIRED)
.build())
.build());
----------
User verification can be used with both discoverable credentials (passkeys) and non-discoverable credentials.
[#autofill-ui]
==== Using passkeys with autofill UI
Passkeys on platform authenticators may also support the WebAuthn
link:https://passkeys.dev/docs/reference/terms/#autofill-ui[autofill UI], also known as "conditional mediation".
This can help onboard users who are unfamiliar with a fully username-less login flow,
allowing a familiar username input field to opportunistically offer a shortcut using a passkey
if the user has one on their device.
This library is compatible with the autofill UI but provides no server-side options for it,
because the steps to enable it are taken on the front-end side.
Using autofill UI does not affect the response verification procedure.
See the link:https://passkeys.dev/docs/use-cases/bootstrapping/[guide on passkeys.dev]
for complete instructions on how to enable the autofill UI.
In particular you need to:
- Add the credential request option `mediation: "conditional"`
alongside the `publicKey` option generated by
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(...)`],
- Add `autocomplete="username webauthn"` to a username input field on the page, and
- Call `navigator.credentials.get()` in the background.
If the Promise resolves, handle it like any other assertion response as described in
<<getting-started-authentication>> above.
Because of technical limitations, autofill UI is as of May 2023 only supported for platform credentials,
i.e., passkeys stored on the user's computing devices.
Autofill UI might support passkeys on external security keys in the future.
[#credential-backup-state]
==== Credential backup state
Some authenticators may allow credentials to be backed up and/or synced between devices.
This capability and its current state is signaled via the
link:https://w3c.github.io/webauthn/#sctn-credential-backup[Credential Backup State] flags,
which are available via the `isBackedUp()` and `isBackupEligible()` methods of
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegistrationResult.html[`RegistrationResult`]
and
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/AssertionResult.html[`AssertionResult`].
These can be used as a hint about how vulnerable a user is to authenticator loss.
In particular, a user with only one credential which is not backed up
may risk getting locked out if they lose their authenticator.
== Migrating from version `1.x`
See link:doc/Migrating_from_v1.adoc[the migration guide].
== Migrating from U2F
This section is only relevant for applications that have user credentials registered via the
link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html[U2F JavaScript API].
New WebAuthn deployments can skip this section.
The WebAuthn API is backwards-compatible with U2F authenticators,
and credentials registered via the U2F API will continue to work with the WebAuthn API with the right settings.
To migrate to using the WebAuthn API, you need to do the following:
1. Follow the link:#getting-started[Getting started] guide above to set up WebAuthn support in general.
+
Note that unlike a U2F AppID, the WebAuthn link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/RelyingPartyIdentity.RelyingPartyIdentityBuilder.html#id(java.lang.String)[RP ID]
consists of only the domain name of the AppID.
WebAuthn does not support link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-appid-and-facets-v1.2-ps-20170411.html[U2F Trusted Facet Lists].
2. Set the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#appId(com.yubico.webauthn.extension.appid.AppId)[`appId()`]
setting on your
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
instance.
The argument to the `appid()` setting should be the same as you used for the `appId` argument to the
link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[U2F `register` and `sign` functions].
+
This will enable the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-extension[`appid`]
and link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension[`appidExclude`]
extensions and configure the `RelyingParty` to accept the given AppId when verifying authenticator signatures.
3. Generate a link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle[user handle]
for each existing user and store it in their account,
or decide on a method for deriving one deterministically from existing user attributes.
For example, if your user records are assigned UUIDs, you can use that UUID as the user handle.
You SHOULD NOT use a plain username or e-mail address, or hash of either, as the user handle -
for more on this, see the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-user-handle-privacy[User Handle Contents]
privacy consideration.
4. When your
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/CredentialRepository.html[`CredentialRepository`]
creates a
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegisteredCredential.html[`RegisteredCredential`]
for a U2F credential,
use the U2F key handle as the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#credentialId(com.yubico.webauthn.data.ByteArray)[credential ID].
If you store key handles base64 encoded, you should decode them using
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/ByteArray.html#fromBase64(java.lang.String)[`ByteArray.fromBase64`]
or
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/data/ByteArray.html#fromBase64Url(java.lang.String)[`ByteArray.fromBase64Url`]
as appropriate before passing them to the `RegisteredCredential`.
5. When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
use the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyEs256Raw(com.yubico.webauthn.data.ByteArray)[`publicKeyEs256Raw()`]
method instead of link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyCose(com.yubico.webauthn.data.ByteArray)[`publicKeyCose()`]
to set the credential public key.
6. Replace calls to the U2F
link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[`register`]
method with calls to `navigator.credentials.create()` as described in link:#getting-started[Getting started].
7. Replace calls to the U2F
link:https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-javascript-api-v1.2-ps-20170411.html#high-level-javascript-api[`sign`]
method with calls to `navigator.credentials.get()` as described in link:#getting-started[Getting started].
Existing U2F credentials should now work with the WebAuthn API.
Note that new credentials registered on U2F authenticators via the WebAuthn API
are NOT backwards compatible with the U2F JavaScript API.
== Architecture
The library tries to place as few requirements on the overall application
architecture as possible. For this reason it is stateless and free from side
effects, and does not directly interact with any database. This means it is
database agnostic and thread safe. The following diagram illustrates an example
architecture for an application using the library.
image::https://raw.githubusercontent.com/Yubico/java-webauthn-server/main/docs/img/demo-architecture.svg?sanitize=true["Example application architecture",align="center"]
The application manages all state and database access, and communicates with the
library via POJO representations of requests and responses. The following
diagram illustrates the data flow during a WebAuthn registration or
authentication ceremony.
image::https://raw.githubusercontent.com/Yubico/java-webauthn-server/main/docs/img/demo-sequence-diagram.svg?sanitize=true["WebAuthn ceremony sequence diagram",align="center"]
In this diagram, the *Client* is the user's browser and the application's
client-side scripts. The *Server* is the application and its business logic, the
*Library* is this library, and the *Users* database stores registered WebAuthn
credentials.
. The client requests to start the ceremony, for example by submitting a form.
The `username` may or may not be known at this point. For example, the user
might be requesting to create a new account, or we might be using
username-less authentication.
. If the user does not already have a
https://www.w3.org/TR/webauthn/#user-handle[user handle], the application
creates one in some application-specific way.
. The application may choose to authenticate the user with a password or the
like before proceeding.
. The application calls one of the library's "start" methods to generate a
parameter object to be passed to `navigator.credentials.create()` or `.get()`
on the client.
. The library generates a random challenge and an assortment of other arguments
depending on configuration set by the application.
. If the `username` is known, the library uses a read-only database adapter
provided by the application to look up the user's credentials.
. The returned list of https://www.w3.org/TR/webauthn/#credential-id[credential
IDs] is used to populate the
https://www.w3.org/TR/webauthn/#dom-publickeycredentialcreationoptions-excludecredentials[`excludeCredentials`]
or
https://www.w3.org/TR/webauthn/#dom-publickeycredentialrequestoptions-allowcredentials[`allowCredentials`]
parameter.
. The library returns a `request` object which can be serialized to JSON and
passed as the `publicKey` argument to `navigator.credentials.create()` or
`.get()`. For registration ceremonies this will be a
https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialcreationoptions[`PublicKeyCredentialCreationOptions`],
and for authentication ceremonies it will be a
https://www.w3.org/TR/webauthn/#dictdef-publickeycredentialrequestoptions[`PublicKeyCredentialRequestOptions`].
The application stores the `request` in temporary storage.
. The application's client-side script runs `navigator.credentials.create()` or
`.get()` with `request` as the `publicKey` argument.
. The user confirms the operation and the client returns a
https://www.w3.org/TR/webauthn/#public-key-credential[`PublicKeyCredential`]
object `response` to the application.
. The application retrieves the `request` from temporary storage and passes
`request` and `response` to one of the library's "finish" methods to run the
response validation logic.
. The library verifies that the `response` contents - challenge, origin, etc. -
are valid.
. If this is an authentication ceremony, the library uses the database adapter
to look up the public key for the credential named in `response.id`.
. The database adapter returns the public key.
. The library verifies the authentication signature.
. The library returns a POJO representation of the result of the ceremony. For
registration ceremonies, this will include the credential ID and public key of
the new credential. The application may opt in to also getting
information about the authenticator model and whether the authenticator
attestation is trusted. For authentication ceremonies, this will include the
username and user handle, the credential ID of the credential used, and the
new https://www.w3.org/TR/webauthn/#signature-counter[signature counter] value
for the credential.
. The application inspects the result object and takes any appropriate actions
as defined by its business logic.
. If the result is not satisfactory, the application reports failure to the
client.
. If the result is satisfactory, the application proceeds with storing the new
credential if this is a registration ceremony.
. If this is an authentication ceremony, the application updates the signature
counter stored in the database for the credential.
. Finally, the application reports success and resumes its business logic.
== Using attestation
WebAuthn supports
link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-attestation[authenticator attestation],
which provides a way for the web service
to request cryptographic proof of what authenticator the user is using.
Most services do not need this, and it is disabled by default.
The link:webauthn-server-attestation[`webauthn-server-attestation` module]
provides optional additional features for working with attestation.
See the module documentation for more details.
Alternatively, you can use the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/attestation/AttestationTrustSource.html[`AttestationTrustSource`]
interface to implement your own source of attestation root certificates
and set it as the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#attestationTrustSource(com.yubico.webauthn.attestation.AttestationTrustSource)[`attestationTrustSource`]
for your
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/RelyingParty.html[`RelyingParty`]
instance.
Note that depending on your JCA provider configuration, you may need to set the
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/attestation/AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder.html#enableRevocationChecking(boolean)[`enableRevocationChecking`]
and/or
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.4.1/com/yubico/webauthn/attestation/AttestationTrustSource.TrustRootsResult.TrustRootsResultBuilder.html#policyTreeValidator(java.util.function.Predicate)[`policyTreeValidator`]
settings for compatibility with some authenticators' attestation certificates.
See the JavaDoc for these settings for more information.
== Building
Use the included
https://docs.gradle.org/current/userguide/gradle_wrapper.html[Gradle wrapper] to
build the `.jar` artifact:
[source,shell]
----------
$ ./gradlew :webauthn-server-core:jar
----------
The output is built in the `webauthn-server-core/build/libs/` directory, and the
version is derived from the most recent Git tag. Builds done on a tagged commit
will have a plain `x.y.z` version number, while a build on any other commit will
result in a version number containing the abbreviated commit hash.
To run the tests:
[source,shell]
----------
$ ./gradlew check
----------
To run the http://pitest.org/[PIT mutation tests] (this may take upwards of 30
minutes):
[source,shell]
----------
$ ./gradlew pitest
----------
[#reproducible-builds]
=== Reproducible builds
Starting in version `1.4.0-RC2`, artifacts are built reproducibly. Fresh builds from
tagged commits should therefore be verifiable by signatures from Maven Central
and GitHub releases:
[source,shell]
----------
$ git checkout 1.4.0-RC2
$ ./gradlew :webauthn-server-core:jar
$ wget https://repo1.maven.org/maven2/com/yubico/webauthn-server-core/1.4.0-RC2/webauthn-server-core-1.4.0-RC2.jar.asc
$ gpg --verify webauthn-server-core-1.4.0-RC2.jar.asc webauthn-server-core/build/libs/webauthn-server-core-1.4.0-RC2.jar
$ wget https://github.com/Yubico/java-webauthn-server/releases/download/1.4.0-RC2/webauthn-server-core-1.4.0-RC2.jar.asc
$ gpg --verify webauthn-server-core-1.4.0-RC2.jar.asc webauthn-server-core/build/libs/webauthn-server-core-1.4.0-RC2.jar
----------
Note that building with a different JDK may produce a different artifact. To
ensure binary reproducibility, please build with the same JDK as specified in
the release notes. Reproducible builds also require building from a Git
repository, since the build embeds version number and Git commit ID into the
built artifacts.
Official Yubico software signing keys are listed on the
https://developers.yubico.com/Software_Projects/Software_Signing.html[Yubico
Developers site].