Skip to content

Commit 5c06fc0

Browse files
committed
Add .isUserVerified() to RegistrationResult and AssertionResult
1 parent 1104940 commit 5c06fc0

File tree

6 files changed

+160
-1
lines changed

6 files changed

+160
-1
lines changed

NEWS

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
== Version 2.4.2 (unreleased) ==
1+
== Version 2.5.0 (unreleased) ==
22

3+
New features:
4+
5+
* Added method `.isUserVerified()` to `RegistrationResult` and `AssertionResult`
6+
as a shortcut for accessing the UV flag in authenticator data.
37
* Updated README and JavaDoc to use the "passkey" term and provide more guidance
48
around passkey use cases.
59

README

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,50 @@ PublicKeyCredentialCreationOptions request = rp.startRegistration(
489489
.build());
490490
----------
491491

492+
You can also request that user verification be used if possible, but is not required:
493+
494+
[source,java]
495+
----------
496+
PublicKeyCredentialCreationOptions request = rp.startRegistration(
497+
StartRegistrationOptions.builder()
498+
.user(/* ... */)
499+
.authenticatorSelection(AuthenticatorSelectionCriteria.builder()
500+
.userVerification(UserVerificationRequirement.PREFERRED)
501+
.build())
502+
.build());
503+
504+
AssertionRequest request = rp.startAssertion(StartAssertionOptions.builder()
505+
.username("alice")
506+
.userVerification(UserVerificationRequirement.PREFERRED)
507+
.build());
508+
----------
509+
510+
In this case
511+
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(...)`]
512+
and
513+
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(...)`]
514+
will NOT enforce user verification,
515+
but instead the `isUserVerified()` method of
516+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.5.0/com/yubico/webauthn/RegistrationResult.html[`RegistrationResult`]
517+
and
518+
link:https://developers.yubico.com/java-webauthn-server/JavaDoc/webauthn-server-core/2.5.0/com/yubico/webauthn/AssertionResult.html[`AssertionResult`]
519+
will tell whether user verification was used.
520+
521+
For example, you could prompt for a password as the second factor if `isUserVerified()` returns `false`:
522+
523+
[source,java]
524+
----------
525+
AssertionResult result = rp.finishAssertion(/* ... */);
526+
527+
if (result.isSuccess()) {
528+
if (result.isUserVerified()) {
529+
return successfulLogin(result.getUsername());
530+
} else {
531+
return passwordRequired(result.getUsername());
532+
}
533+
}
534+
----------
535+
492536
User verification can be used with both discoverable credentials (passkeys) and non-discoverable credentials.
493537

494538

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
3232
import com.yubico.webauthn.data.AuthenticatorAttachment;
3333
import com.yubico.webauthn.data.AuthenticatorData;
34+
import com.yubico.webauthn.data.AuthenticatorDataFlags;
35+
import com.yubico.webauthn.data.AuthenticatorResponse;
3436
import com.yubico.webauthn.data.ByteArray;
3537
import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
3638
import com.yubico.webauthn.data.PublicKeyCredential;
@@ -144,6 +146,25 @@ public ByteArray getUserHandle() {
144146
return credential.getUserHandle();
145147
}
146148

149+
/**
150+
* Check whether the <a href="https://www.w3.org/TR/webauthn/#user-verification">user
151+
* verification</a> as performed during the authentication ceremony.
152+
*
153+
* <p>This flag is also available via <code>
154+
* {@link PublicKeyCredential}.{@link PublicKeyCredential#getResponse() getResponse()}.{@link AuthenticatorResponse#getParsedAuthenticatorData() getParsedAuthenticatorData()}.{@link AuthenticatorData#getFlags() getFlags()}.{@link AuthenticatorDataFlags#UV UV}
155+
* </code>.
156+
*
157+
* @return <code>true</code> if and only if the authenticator claims to have performed user
158+
* verification during the authentication ceremony.
159+
* @see <a href="https://www.w3.org/TR/webauthn/#user-verification">User Verification</a>
160+
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-uv">UV flag in §6.1. Authenticator
161+
* Data</a>
162+
*/
163+
@JsonIgnore
164+
public boolean isUserVerified() {
165+
return credentialResponse.getResponse().getParsedAuthenticatorData().getFlags().UV;
166+
}
167+
147168
/**
148169
* Check whether the asserted credential is <a
149170
* href="https://w3c.github.io/webauthn/#backup-eligible">backup eligible</a>, using the <a

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@
3333
import com.yubico.webauthn.data.AttestationType;
3434
import com.yubico.webauthn.data.AuthenticatorAttachment;
3535
import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
36+
import com.yubico.webauthn.data.AuthenticatorData;
37+
import com.yubico.webauthn.data.AuthenticatorDataFlags;
3638
import com.yubico.webauthn.data.AuthenticatorRegistrationExtensionOutputs;
39+
import com.yubico.webauthn.data.AuthenticatorResponse;
3740
import com.yubico.webauthn.data.ByteArray;
3841
import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
3942
import com.yubico.webauthn.data.PublicKeyCredential;
@@ -125,6 +128,25 @@ private static RegistrationResult fromJson(
125128
.collect(Collectors.toList())));
126129
}
127130

131+
/**
132+
* Check whether the <a href="https://www.w3.org/TR/webauthn/#user-verification">user
133+
* verification</a> as performed during the registration ceremony.
134+
*
135+
* <p>This flag is also available via <code>
136+
* {@link PublicKeyCredential}.{@link PublicKeyCredential#getResponse() getResponse()}.{@link AuthenticatorResponse#getParsedAuthenticatorData() getParsedAuthenticatorData()}.{@link AuthenticatorData#getFlags() getFlags()}.{@link AuthenticatorDataFlags#UV UV}
137+
* </code>.
138+
*
139+
* @return <code>true</code> if and only if the authenticator claims to have performed user
140+
* verification during the registration ceremony.
141+
* @see <a href="https://www.w3.org/TR/webauthn/#user-verification">User Verification</a>
142+
* @see <a href="https://w3c.github.io/webauthn/#authdata-flags-uv">UV flag in §6.1. Authenticator
143+
* Data</a>
144+
*/
145+
@JsonIgnore
146+
public boolean isUserVerified() {
147+
return credential.getResponse().getParsedAuthenticatorData().getFlags().UV;
148+
}
149+
128150
/**
129151
* Check whether the created credential is <a
130152
* href="https://w3c.github.io/webauthn/#backup-eligible">backup eligible</a>, using the <a

webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyAssertionSpec.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,43 @@ class RelyingPartyAssertionSpec
25752575
.username(user.getName)
25762576
.build()
25772577

2578+
it("exposes isUserVerified() with the UV flag value in authenticator data.") {
2579+
val pkcWithoutUv =
2580+
TestAuthenticator.createAssertion(
2581+
flags = Some(new AuthenticatorDataFlags(0x00.toByte)),
2582+
challenge =
2583+
request.getPublicKeyCredentialRequestOptions.getChallenge,
2584+
credentialKey = credentialKeypair,
2585+
credentialId = credential.getId,
2586+
)
2587+
val pkcWithUv =
2588+
TestAuthenticator.createAssertion(
2589+
flags = Some(new AuthenticatorDataFlags(0x04.toByte)),
2590+
challenge =
2591+
request.getPublicKeyCredentialRequestOptions.getChallenge,
2592+
credentialKey = credentialKeypair,
2593+
credentialId = credential.getId,
2594+
)
2595+
2596+
val resultWithoutUv = rp.finishAssertion(
2597+
FinishAssertionOptions
2598+
.builder()
2599+
.request(request)
2600+
.response(pkcWithoutUv)
2601+
.build()
2602+
)
2603+
val resultWithUv = rp.finishAssertion(
2604+
FinishAssertionOptions
2605+
.builder()
2606+
.request(request)
2607+
.response(pkcWithUv)
2608+
.build()
2609+
)
2610+
2611+
resultWithoutUv.isUserVerified should be(false)
2612+
resultWithUv.isUserVerified should be(true)
2613+
}
2614+
25782615
it("exposes isBackupEligible() with the BE flag value in authenticator data.") {
25792616
val pkcWithoutBackup =
25802617
TestAuthenticator.createAssertion(

webauthn-server-core/src/test/scala/com/yubico/webauthn/RelyingPartyRegistrationSpec.scala

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4623,6 +4623,37 @@ class RelyingPartyRegistrationSpec
46234623
.pubKeyCredParams(List(PublicKeyCredentialParameters.ES256).asJava)
46244624
.build()
46254625

4626+
it("exposes isUserVerified() with the UV flag value in authenticator data.") {
4627+
val (pkcWithoutUv, _, _) =
4628+
TestAuthenticator.createUnattestedCredential(
4629+
flags = Some(new AuthenticatorDataFlags(0x00.toByte)),
4630+
challenge = request.getChallenge,
4631+
)
4632+
val (pkcWithUv, _, _) =
4633+
TestAuthenticator.createUnattestedCredential(
4634+
flags = Some(new AuthenticatorDataFlags(0x04.toByte)),
4635+
challenge = request.getChallenge,
4636+
)
4637+
4638+
val resultWithoutUv = rp.finishRegistration(
4639+
FinishRegistrationOptions
4640+
.builder()
4641+
.request(request)
4642+
.response(pkcWithoutUv)
4643+
.build()
4644+
)
4645+
val resultWithUv = rp.finishRegistration(
4646+
FinishRegistrationOptions
4647+
.builder()
4648+
.request(request)
4649+
.response(pkcWithUv)
4650+
.build()
4651+
)
4652+
4653+
resultWithoutUv.isUserVerified should be(false)
4654+
resultWithUv.isUserVerified should be(true)
4655+
}
4656+
46264657
it("exposes isBackupEligible() with the BE flag value in authenticator data.") {
46274658
val (pkcWithoutBackup, _, _) =
46284659
TestAuthenticator.createUnattestedCredential(

0 commit comments

Comments
 (0)