@@ -25,15 +25,15 @@ Maven:
25
25
<dependency>
26
26
<groupId>com.yubico</groupId>
27
27
<artifactId>webauthn-server-core</artifactId>
28
- <version>1.12.1 </version>
28
+ <version>1.12.2 </version>
29
29
<scope>compile</scope>
30
30
</dependency>
31
31
----------
32
32
33
33
Gradle:
34
34
35
35
----------
36
- compile 'com.yubico:webauthn-server-core:1.12.1 '
36
+ compile 'com.yubico:webauthn-server-core:1.12.2 '
37
37
----------
38
38
39
39
=== Semantic versioning
@@ -110,10 +110,7 @@ The client side involves:
110
110
passing the result from `RelyingParty.startRegistration(...)` or `.startAssertion(...)` as the argument.
111
111
2. Encode the result of the successfully resolved promise and return it to the server.
112
112
For this you need some way to encode `Uint8Array` values;
113
- this guide will assume use of link:https://github.com/beatgammit/base64-js[base64-js].
114
- However the built-in parser methods for
115
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/data/PublicKeyCredential.html[`PublicKeyCredential`]
116
- expect URL-safe Base64 encoding, so the below examples will include this as an additional step.
113
+ this guide will use GitHub's link:https://github.com/github/webauthn-json[webauthn-json] library.
117
114
118
115
Example code is given below.
119
116
For more detailed example usage, see
@@ -163,6 +160,8 @@ A registration ceremony consists of 5 main steps:
163
160
4. Validate the response using `RelyingParty.finishRegistration(...)`.
164
161
5. Update your database using the `finishRegistration` output.
165
162
163
+ 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.
164
+
166
165
First, generate registration parameters and send them to the client:
167
166
168
167
[source,java]
@@ -196,62 +195,23 @@ Now call the WebAuthn API on the client side:
196
195
197
196
[source,javascript]
198
197
----------
199
- function base64urlToUint8array(base64Bytes) {
200
- const padding = '===='.substring(0, (4 - (base64Bytes.length % 4)) % 4);
201
- return base64js.toByteArray((base64Bytes + padding).replace(/\//g, "_").replace(/\+/g, "-"));
202
- }
203
- function uint8arrayToBase64url(bytes) {
204
- if (bytes instanceof Uint8Array) {
205
- return base64js.fromByteArray(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
206
- } else {
207
- return uint8arrayToBase64url(new Uint8Array(bytes));
208
- }
209
- }
210
-
211
- fetch(/* ... */) // Make the call that returns the credentialCreateJson above
212
- .then(credentialCreateJson => ({ // Decode byte arrays from base64url
213
- publicKey: {
214
- ...credentialCreateJson.publicKey,
215
-
216
- challenge: base64urlToUint8array(credentialCreateJson.publicKey.challenge),
217
- user: {
218
- ...credentialCreateJson.publicKey.user,
219
- id: base64urlToUint8array(credentialCreateJson.publicKey.user.id),
220
- },
221
- excludeCredentials: credentialCreateJson.publicKey.excludeCredentials.map(credential => ({
222
- ...credential,
223
- id: base64urlToUint8array(credential.id),
224
- })),
225
-
226
- // Warning: Extension inputs could also contain binary data that needs encoding
227
- extensions: credentialCreateJson.publicKey.extensions,
228
- },
229
- }))
230
- .then(credentialCreateOptions => // Call WebAuthn ceremony
231
- navigator.credentials.create(credentialCreateOptions))
232
- .then(publicKeyCredential => ({ // Encode PublicKeyCredential for transport to server (example)
233
- type: publicKeyCredential.type,
234
- id: publicKeyCredential.id,
235
- response: {
236
- attestationObject: uint8arrayToBase64url(publicKeyCredential.response.attestationObject),
237
- clientDataJSON: uint8arrayToBase64url(publicKeyCredential.response.clientDataJSON),
238
-
239
- // Attempt to read transports, but recover gracefully if not supported by the browser
240
- transports: publicKeyCredential.response.getTransports && publicKeyCredential.response.getTransports() || [],
241
- },
242
-
243
- // Warning: Client extension results could also contain binary data that needs encoding
244
- clientExtensionResults: publicKeyCredential.getClientExtensionResults(),
245
- }))
246
- .then(encodedResult =>
247
- fetch(/* ... */)); // Return encoded PublicKeyCredential to server
198
+ import * as webauthnJson from "@github/webauthn-json";
199
+
200
+ // Make the call that returns the credentialCreateJson above
201
+ const credentialCreateOptions = await fetch(/* ... */).then(resp => resp.json());
202
+
203
+ // Call WebAuthn ceremony using webauthn-json wrapper
204
+ const publicKeyCredential = await webauthnJson.create(credentialCreateOptions);
205
+
206
+ // Return encoded PublicKeyCredential to server
207
+ fetch(/* ... */, { body: JSON.stringify(publicKeyCredential) });
248
208
----------
249
209
250
210
Validate the response on the server side:
251
211
252
212
[source,java]
253
213
----------
254
- String publicKeyCredentialJson = /* ... */; // encodedResult from client
214
+ String publicKeyCredentialJson = /* ... */; // publicKeyCredential from client
255
215
PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> pkc =
256
216
PublicKeyCredential.parseRegistrationResponseJson(publicKeyCredentialJson);
257
217
@@ -295,6 +255,8 @@ Like registration ceremonies, an authentication ceremony consists of 5 main step
295
255
4. Validate the response using `RelyingParty.finishAssertion(...)`.
296
256
5. Update your database using the `finishAssertion` output, and act upon the result (for example, grant login access).
297
257
258
+ 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.
259
+
298
260
First, generate authentication parameters and send them to the client:
299
261
300
262
[source,java]
@@ -313,58 +275,23 @@ Now call the WebAuthn API on the client side:
313
275
314
276
[source,javascript]
315
277
----------
316
- function base64urlToUint8array(base64Bytes) {
317
- const padding = '===='.substring(0, (4 - (base64Bytes.length % 4)) % 4);
318
- return base64js.toByteArray((base64Bytes + padding).replace(/\//g, "_").replace(/\+/g, "-"));
319
- }
320
- function uint8arrayToBase64url(bytes) {
321
- if (bytes instanceof Uint8Array) {
322
- return base64js.fromByteArray(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
323
- } else {
324
- return uint8arrayToBase64url(new Uint8Array(bytes));
325
- }
326
- }
327
-
328
- fetch(/* ... */) // Make the call that returns the credentialGetJson above
329
- .then(credentialGetJson => ({ // Decode byte arrays from base64url
330
- publicKey: {
331
- ...credentialGetJson.publicKey,
332
- allowCredentials: credentialGetJson.publicKey.allowCredentials
333
- && credentialGetJson.publicKey.allowCredentials.map(credential => ({
334
- ...credential,
335
- id: base64urlToUint8array(credential.id),
336
- })),
337
-
338
- challenge: base64urlToUint8array(credentialGetJson.publicKey.challenge),
339
-
340
- // Warning: Extension inputs could also contain binary data that needs encoding
341
- extensions: credentialGetJson.publicKey.extensions,
342
- },
343
- }))
344
- .then(credentialGetOptions => // Call WebAuthn ceremony
345
- navigator.credentials.get(credentialGetOptions))
346
- .then(publicKeyCredential => ({ // Encode PublicKeyCredential for transport to server (example)
347
- type: publicKeyCredential.type,
348
- id: publicKeyCredential.id,
349
- response: {
350
- authenticatorData: uint8arrayToBase64url(publicKeyCredential.response.authenticatorData),
351
- clientDataJSON: uint8arrayToBase64url(publicKeyCredential.response.clientDataJSON),
352
- signature: uint8arrayToBase64url(publicKeyCredential.response.signature),
353
- userHandle: publicKeyCredential.response.userHandle && uint8arrayToBase64url(publicKeyCredential.response.userHandle),
354
- },
355
-
356
- // Warning: Client extension results could also contain binary data that needs encoding
357
- clientExtensionResults: publicKeyCredential.getClientExtensionResults(),
358
- }))
359
- .then(encodedResult =>
360
- fetch(/* ... */)); // Return encoded PublicKeyCredential to server
278
+ import * as webauthnJson from "@github/webauthn-json";
279
+
280
+ // Make the call that returns the credentialGetJson above
281
+ const credentialGetOptions = await fetch(/* ... */).then(resp => resp.json());
282
+
283
+ // Call WebAuthn ceremony using webauthn-json wrapper
284
+ const publicKeyCredential = await webauthnJson.get(credentialGetOptions);
285
+
286
+ // Return encoded PublicKeyCredential to server
287
+ fetch(/* ... */, { body: JSON.stringify(publicKeyCredential) });
361
288
----------
362
289
363
290
Validate the response on the server side:
364
291
365
292
[source,java]
366
293
----------
367
- String publicKeyCredentialJson = /* ... */; // encodedResult from client
294
+ String publicKeyCredentialJson = /* ... */; // publicKeyCredential from client
368
295
PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkc =
369
296
PublicKeyCredential.parseAssertionResponseJson(publicKeyCredentialJson);
370
297
@@ -473,13 +400,13 @@ and credentials registered via the U2F API will continue to work with the WebAut
473
400
474
401
To migrate to using the WebAuthn API, you need to do the following:
475
402
476
- * Follow the link:#getting-started[Getting started] guide above to set up WebAuthn support in general.
403
+ 1. Follow the link:#getting-started[Getting started] guide above to set up WebAuthn support in general.
477
404
+
478
405
Note that unlike a U2F AppID, the WebAuthn link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/data/RelyingPartyIdentity.RelyingPartyIdentityBuilder.html#id(java.lang.String)[RP ID]
479
406
consists of only the domain name of the AppID.
480
407
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].
481
408
482
- * Set the
409
+ 2. Set the
483
410
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RelyingParty.RelyingPartyBuilder.html#appId(com.yubico.webauthn.extension.appid.AppId)[`appId()`]
484
411
setting on your `RelyingParty` instance.
485
412
The argument to the `appid()` setting should be the same as you used for the `appId` argument to the
@@ -489,36 +416,36 @@ This will enable the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sc
489
416
and link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-appid-exclude-extension[`appidExclude`]
490
417
extensions and configure the `RelyingParty` to accept the given AppId when verifying authenticator signatures.
491
418
492
- * Generate a link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle[user handle] for each existing user
493
- and store it in their account,
494
- or decide on a method for deriving one deterministically from existing user attributes.
495
- For example, if your user records are assigned UUIDs, you can use that UUID as the user handle.
496
- You SHOULD NOT use a plain username or e-mail address, or hash of either, as the user handle -
497
- for more on this, see the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-user-handle-privacy[User Handle Contents]
498
- privacy consideration.
499
-
500
- * When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
501
- use the U2F key handle as the
502
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#credentialId(com.yubico.webauthn.data.ByteArray)[credential ID].
503
- If you store key handles base64 encoded, you should decode them using
504
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/data/ByteArray.html#fromBase64(java.lang.String)[`ByteArray.fromBase64`]
505
- or
506
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/data/ByteArray.html#fromBase64Url(java.lang.String)[`ByteArray.fromBase64Url`]
507
- as appropriate before passing them to the `RegisteredCredential`.
508
-
509
- * When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
510
- use the
511
- link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyEs256Raw(com.yubico.webauthn.data.ByteArray)[`publicKeyEs256Raw()`]
512
- method instead of link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyCose(com.yubico.webauthn.data.ByteArray)[`publicKeyCose()`]
513
- to set the credential public key.
514
-
515
- * Replace calls to the U2F
516
- 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`]
517
- method with calls to `navigator.credentials.create()` as described in link:#getting-started[Getting started].
518
-
519
- * Replace calls to the U2F
520
- 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`]
521
- method with calls to `navigator.credentials.get()` as described in link:#getting-started[Getting started].
419
+ 3. Generate a link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#user-handle[user handle]
420
+ for each existing user and store it in their account,
421
+ or decide on a method for deriving one deterministically from existing user attributes.
422
+ For example, if your user records are assigned UUIDs, you can use that UUID as the user handle.
423
+ You SHOULD NOT use a plain username or e-mail address, or hash of either, as the user handle -
424
+ for more on this, see the link:https://www.w3.org/TR/2021/REC-webauthn-2-20210408/#sctn-user-handle-privacy[User Handle Contents]
425
+ privacy consideration.
426
+
427
+ 4. When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
428
+ use the U2F key handle as the
429
+ link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#credentialId(com.yubico.webauthn.data.ByteArray)[credential ID].
430
+ If you store key handles base64 encoded, you should decode them using
431
+ link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/data/ByteArray.html#fromBase64(java.lang.String)[`ByteArray.fromBase64`]
432
+ or
433
+ link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/data/ByteArray.html#fromBase64Url(java.lang.String)[`ByteArray.fromBase64Url`]
434
+ as appropriate before passing them to the `RegisteredCredential`.
435
+
436
+ 5. When your `CredentialRepository` creates a `RegisteredCredential` for a U2F credential,
437
+ use the
438
+ link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyEs256Raw(com.yubico.webauthn.data.ByteArray)[`publicKeyEs256Raw()`]
439
+ method instead of link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core-minimal/latest/com/yubico/webauthn/RegisteredCredential.RegisteredCredentialBuilder.html#publicKeyCose(com.yubico.webauthn.data.ByteArray)[`publicKeyCose()`]
440
+ to set the credential public key.
441
+
442
+ 6. Replace calls to the U2F
443
+ 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`]
444
+ method with calls to `navigator.credentials.create()` as described in link:#getting-started[Getting started].
445
+
446
+ 7. Replace calls to the U2F
447
+ 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`]
448
+ method with calls to `navigator.credentials.get()` as described in link:#getting-started[Getting started].
522
449
523
450
Existing U2F credentials should now work with the WebAuthn API.
524
451
0 commit comments