[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-42 - credential policies are not evaluated for all keys

Phil Smart philip.smart at jisc.ac.uk
Mon Jan 27 15:42:51 UTC 2025


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-idp-plugin-webauthn.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-webauthn.git;a=commit;h=8ee5d6ddfa320443c2af54252bb90ba362eced25

The following commit(s) were added to refs/heads/main by this push:
     new 8ee5d6d  JWEBAUTHN-42 - credential policies are not evaluated for all keys
8ee5d6d is described below

commit 8ee5d6ddfa320443c2af54252bb90ba362eced25
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Mon Jan 27 15:42:47 2025 +0000

    JWEBAUTHN-42 - credential policies are not evaluated for all keys
    
     - Added a injectable strategy to find the userHandle used to filter
    registered credentials to that which signed the assertion.
     - The default strategy uses either the userHandle from the assertion
    itself, or the userId stored in the context (which came from the initial
    username to credential lookup).
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-42
---
 .../authn/webauthn/impl/CheckCredentialPolicy.java |  61 +++--
 .../impl/DefaultUserHandleLookupStrategy.java      |  78 +++++++
 .../webauthn/impl/LookupRegisteredCredentials.java |   4 +-
 .../YubicoWebauthnAuthenticationClientTest.java    |  10 +-
 .../webauthn/flow/AbstractWebAuthnFlowTest.java    |  19 +-
 .../webauthn/flow/TestAdminManagementFlow.java     |   2 +-
 .../authn/webauthn/flow/TestPasswordlessFlow.java  |  61 ++++-
 ...ow.java => TestPasswordlessFlowWithPolicy.java} |  91 +++++---
 .../authn/webauthn/flow/TestRegistrationFlow.java  |   2 +-
 .../authn/webauthn/flow/TestSecondFactorFlow.java  |   2 +-
 .../authn/webauthn/flow/TestUsernameslessFlow.java |  17 +-
 .../flow/TestUsernameslessFlowWithPolicy.java      |   4 +-
 ...essWithPolicyApplicationContextInitializer.java |  50 ++++
 .../authn/webauthn/impl/AbstractWebAuthnTest.java  |   2 +-
 .../webauthn/impl/CheckCredentialPolicyTest.java   | 253 +++++++++++++++++++++
 ...kupRegisteredCredentialsFromUserHandleTest.java |   8 +-
 .../impl/ValidateWebAuthnAssertionTest.java        |   4 +-
 .../policy/impl/RejectAllCredentialPolicyRule.java |  35 +++
 .../webauthn/storage/impl/MockAuthenticator.java   |  12 +-
 .../test-beans-authentication-reject-policy.xml    |  31 +++
 20 files changed, 662 insertions(+), 84 deletions(-)

diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckCredentialPolicy.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckCredentialPolicy.java
index a7e4803..663c936 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckCredentialPolicy.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckCredentialPolicy.java
@@ -15,7 +15,8 @@
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
 import java.util.Collection;
-import java.util.Optional;
+import java.util.List;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -43,6 +44,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialReposi
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -62,6 +64,9 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
     @NonnullBeforeExec 
     private PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion;
     
+    /** Get the userHandle to help find the credential used to sign the assertion.*/ 
+    @NonnullAfterInit private Function<ProfileRequestContext, byte[]> userHandleLookupStrategy;
+    
     /** The credential policy to check.*/
     @Nullable private CredentialPolicy credentialPolicy;
     
@@ -77,6 +82,7 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
     protected CheckCredentialPolicy() {
         super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
                 compose(new ChildContextLookup<>(AuthenticationContext.class)));
+        userHandleLookupStrategy = new DefaultUserHandleLookupStrategy();
     }
     
     /**
@@ -89,6 +95,18 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
         credentialPolicy = policy;
     }
     
+    /**
+     * Set the strategy to locate the userHandle of the user of the credential used to sign the 
+     * assertion.
+     * 
+     * @param strategy The user handle lookup strategy to set.
+     */
+    public void setUserHandleLookupStrategy(final Function<ProfileRequestContext, byte[]> strategy) {
+        checkSetterPreconditions();
+        userHandleLookupStrategy = Constraint.isNotNull(strategy,
+                "UserHandle lookup strategy can not be null");
+    }
+    
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -133,45 +151,58 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
             // If no policy, nothing can be applied
             log.trace("{} No authenticator policy to apply", getLogPrefix());
             return;
-        }        
-       
-        final Optional<ByteArray> userHandle = assertion.getResponse().getUserHandle();
-        if (userHandle.isEmpty()) {
-            log.trace("{} UserHandle could not be found in the response",getLogPrefix());
+        }
+        
+        final byte[] userHandleBytes = userHandleLookupStrategy.apply(profileRequestContext);      
+        if (userHandleBytes == null) {
+            log.debug("{} UserHandle could not be found, policy can not be applied",getLogPrefix());
             return;
         }
         
         final Collection<EnhancedCredentialRecord> registeredCredentials = context.getExistingCredentials();
         
-        final Optional<EnhancedCredentialRecord> credential = registeredCredentials.stream().filter(cred -> {
+        final ByteArray userHandle = new ByteArray(userHandleBytes);
+        final List<EnhancedCredentialRecord> credentials = registeredCredentials.stream().filter(cred -> {
             final CredentialRecord credRecord = cred.getCredentialRecord();
             return assertion.getId().equals(credRecord.getCredential().getCredentialId()) &&
-                    userHandle.get().equals(credRecord.getUserIdentity().getId());
-        }).findFirst();
+                    userHandle.equals(credRecord.getUserIdentity().getId());
+        }).toList();
 
-        if (credential.isEmpty()) {
-            log.trace("{} UserHandle '{}' has no registered credential",getLogPrefix(), userHandle.get().getHex());
+        if (credentials.isEmpty()) {
+            log.trace("{} UserHandle '{}' has no registered credential, policy can not be applied",getLogPrefix(), 
+                    userHandle.getHex());
+            return;
+        }
+        
+        // Reject if more than one credential that matches the credentialID and userHandle in the assertion. 
+        if (credentials.size() != 1) {
+            log.debug("{} Credential ID '{}' for userHandle '{}' has more than one registered credential, "
+                    + "policy can not be applied, rejecting",getLogPrefix(), assertion.getId(),
+                    userHandle.getHex());
+            authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
+                    WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+            ActionSupport.buildEvent(profileRequestContext, WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
             return;
         }
         
-        final EnhancedCredentialRecord credentialToEvaluate = credential.get();
+        final EnhancedCredentialRecord credentialToEvaluate = credentials.get(0);
         assert credentialToEvaluate != null;
         final CredentialPolicyOutcome outcome = localPolicy.evaluate(credentialToEvaluate, profileRequestContext);
         
         if (outcome == CredentialPolicyOutcome.REJECT) {
             log.warn("{} CredentialPolicy '{}' has rejected credential '{}'", getLogPrefix(), localPolicy.getId(),
-                    credential.get().getCredentialRecord().getCredentialIdBase64());
+                    credentialToEvaluate.getCredentialRecord().getCredentialIdBase64());
             authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
                     WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
             ActionSupport.buildEvent(profileRequestContext, WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
             return;
         } else if (outcome == CredentialPolicyOutcome.IGNORE) {
             log.trace("{} CredentialPolicy '{}' was not active for credential '{}', accepting", getLogPrefix(), 
-                    localPolicy.getId(), credential.get().getCredentialRecord().getCredentialIdBase64());
+                    localPolicy.getId(), credentialToEvaluate.getCredentialRecord().getCredentialIdBase64());
             return;
         }
         log.debug("{} CredentialPolicy '{}' accepted credential '{}'", getLogPrefix(), localPolicy.getId(),
-                credential.get().getCredentialRecord().getCredentialIdBase64());
+                credentialToEvaluate.getCredentialRecord().getCredentialIdBase64());
              
     } 
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/DefaultUserHandleLookupStrategy.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/DefaultUserHandleLookupStrategy.java
new file mode 100644
index 0000000..827ffaa
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/DefaultUserHandleLookupStrategy.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A strategy to locate the userHandle first from an assertion response and then, if that does not exist, from the
+ * userId in the WebAuthn authentication context.
+ */
+ at ThreadSafe
+public class DefaultUserHandleLookupStrategy implements Function<ProfileRequestContext, byte[]> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultUserHandleLookupStrategy.class);
+
+    /** {@inheritDoc} */
+    @Override
+    public byte[] apply(final ProfileRequestContext prc) {
+        
+        final AuthenticationContext authnCtx = prc.getSubcontext(AuthenticationContext.class);
+        if (authnCtx == null) {
+            return null;
+        }
+        final WebAuthnAuthenticationContext webAuthnContext = 
+                authnCtx.getSubcontext(WebAuthnAuthenticationContext.class);
+        if (webAuthnContext == null) {
+            return null;
+        }
+        
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion = 
+                webAuthnContext.getPublicKeyCredentialAssertionResponse();
+        
+        if (assertion != null && assertion.getResponse() != null 
+                && assertion.getResponse().getUserHandle().isPresent()) {
+            
+            // Find first from assertion response if the userHandle exists
+            log.trace("Found userHandle from public key credential assertion");
+            return assertion.getResponse().getUserHandle().get().getBytes();
+           
+        } else if (webAuthnContext.getUserId() != null){
+            // Find from the userId in the context if it exists
+            log.trace("Found userHandle from userId in authentication context");
+            return webAuthnContext.getUserId();
+        } else {
+            return null;
+        }
+        
+
+    }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index 3be8627..2a14de7 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
@@ -168,8 +168,6 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebA
         final Collection<CredentialRecord> credentials =
                 repository.getRegistrationsByUsername(username);   
 
-        log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(), credentials.size(), username);
-        
         context.setExistingCredentials(enhancedCredentialRecord(credentials));
         
         final Optional<ByteArray> userHandle = repository.getUserHandleForUsername(username);
@@ -178,6 +176,8 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebA
             assert userId != null;
             context.setUserId(userId);
         }
+        log.debug("{} Found '{}' registered credential(s) for user '{}' with userId '{}'", getLogPrefix(), 
+                credentials.size(), username, userHandle.isPresent() ? userHandle.get().getBase64() : "");
         
         if (triggerEventOnNoCredentialsPredicate.test(profileRequestContext) && credentials.isEmpty()) {
             log.debug("{} Triggering event '{}' ", getLogPrefix(), noCredentialsEventId);
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
index ac153fd..e2ba9c7 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
@@ -200,7 +200,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
 
         final AssertionResult result = 
                 client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
@@ -226,7 +226,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(
                     credentialRegistration.getCredential().getCredentialId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
 
         // The username used here is not the same as has user that has the credential registered for
         final AssertionResult result = 
@@ -274,7 +274,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
         client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
                 credentialRequestOptions, assertion);
@@ -319,7 +319,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
         assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                clientDataGet, null);
+                clientDataGet, null, false);
     
         client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
             credentialRequestOptions, assertion);
@@ -365,7 +365,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
         assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                clientDataGet, null);
+                clientDataGet, null, false);
     
         client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
             credentialRequestOptions, assertion);
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
index f9822f7..a5c4dde 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/AbstractWebAuthnFlowTest.java
@@ -485,25 +485,29 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
        return assertion;
     }
     
+    
     /**
      * Create a credential authentication assertion response from the mock authenticator.
      * 
      * @param credentialId the credential ID to find the correct credential to sign the request with
      * @param challenge the challenge to use
+     * @param blankUserHandle should the userHandle be empty in the assertion? this can happen for passwordless or 2FA
+     * authentications.
      * @return the credential registration 
      * 
      * @throws Exception on error
      */
     protected PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
                 createAssertionReponseFrom(
-                        final byte[] credentialId, final byte[] challenge) throws Exception {
+                        final byte[] credentialId, final byte[] challenge, final boolean blankUserHandle) 
+                                throws Exception {
 
-        return createAssertionReponseFrom(credentialId, challenge, ORIGIN);
+        return createAssertionReponseFrom(credentialId, challenge, ORIGIN, blankUserHandle);
     }
     
     /**
@@ -512,6 +516,8 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
      * @param credentialId the credential ID to find the correct credential to sign the request with
      * @param challenge the challenge to use
      * @param the origin to use
+     * @param blankUserHandle should the userHandle be empty in the assertion? this can happen for passwordless or 2FA
+     * authentications.
      * 
      * @return the credential registration 
      * 
@@ -519,7 +525,8 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
      */
     protected PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
                 createAssertionReponseFrom(
-                        final byte[] credentialId, final byte[] challenge, final String origin) throws Exception {
+                        final byte[] credentialId, final byte[] challenge, final String origin,
+                        final boolean blankUserHandle) throws Exception {
 
         final String challengeB64 = Base64Support.encodeURLSafe(challenge);
         final Map<String, String> clientDataGet = createClientData("webauthn.get", origin, challengeB64); 
@@ -527,7 +534,7 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(credentialId, 
-                    clientDataGet, null);
+                    clientDataGet, null, blankUserHandle);
         
        return assertion;
     }
@@ -556,7 +563,7 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(credentialId, 
-                    clientDataGet, userHandle);
+                    clientDataGet, userHandle, false);
         
        return assertion;
     }
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
index c4a99ce..603a408 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestAdminManagementFlow.java
@@ -147,7 +147,7 @@ public class TestAdminManagementFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlow.java
index 1de07d9..57e6bea 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlow.java
@@ -100,7 +100,66 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
+
+        final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
+        
+        // Re-set external context to holder
+        ExternalContextHolder.setExternalContext(externalContext);
+        setHttpFormRequest("POST", Map.of(ExtractPublicKeyCredentialAssertionFromFormRequest.DEFAULT_PARAMETER_NAME, 
+                assertionResponseJson));
+        externalContext.setEventId("proceed");
+        result.getSecond().setCurrentState("DisplayWebAuthnView");
+        result.getSecond().resume(externalContext);
+        
+        // assert end conditions, no existing result or principal. So this should produce a Username principal
+        assertEquals(result.getSecond().getOutcome().getId(), "proceed");
+        assertAuthenticationSuccessConditions(prc, true);
+        
+    }
+    
+    /* 
+     * Tests the passwordless flow when there is not userHandle in the assertion response. Which is allowable if
+     * the 'allowedCredentials' are supplied to the WebAuthn get request.
+     */
+    @SuppressWarnings("null")
+    @Test
+    public void testPasswordlessFlow_NoUserHandleInAssertion() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+        credentialRepo.addRegistrationByUsername(USERNAME, registration);
+        
+        final var prc = buildProfileRequestContext(false, false, USERNAME);
+
+        final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
+                addToConversationScopeMap(Map.of("opensamlProfileRequestContext", prc)));
+
+        assertFlowExecutionActive(result.getSecond());
+        assertCurrentStateEquals("CollectUsernameView", result.getSecond());
+        
+        // Do next part of flow, populate the username
+        ExternalContextHolder.setExternalContext(externalContext);
+        setHttpFormRequest("POST", Map.of("j_username", USERNAME));
+        externalContext.setEventId("proceed");
+        
+        result.getSecond().setCurrentState("CollectUsernameView");
+        result.getSecond().resume(externalContext);
+        
+        assertFlowExecutionActive(result.getSecond());
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        assertPublicKeyCredentialRequestOptions(prc, true, true);
+        
+        // Do assertion validation half of flow
+        
+        // Get the challenge that was set into the PublicKeyCredentialRequestOptions. 
+        // otherwise we will end up signing a different one, which will provide its own test.
+        final WebAuthnAuthenticationContext authnContext = getWebAuthnAuthenticationContext(prc);
+        final ByteArray challenge = authnContext.getPublicKeyCredentialRequestOptions().getChallenge();
+        
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+        assertionResponse = createAssertionReponseFrom(
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), true);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
similarity index 57%
copy from webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlow.java
copy to webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
index e230574..f93b363 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
@@ -14,12 +14,9 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.flow;
 
-import static org.testng.Assert.assertEquals;
-
 import java.util.Map;
 
 import javax.annotation.Nonnull;
-import javax.security.auth.Subject;
 
 import org.springframework.test.context.ContextConfiguration;
 import org.springframework.webflow.context.ExternalContextHolder;
@@ -32,9 +29,6 @@ import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 
-import net.shibboleth.idp.authn.AuthenticationResult;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.authn.context.MultiFactorAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.ExtractPublicKeyCredentialAssertionFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
@@ -42,19 +36,20 @@ import net.shibboleth.shared.collection.Pair;
 
 
 /**
- * Flow tests for the 2fa flow.
+ * Flow tests for the passwordless flow.
  */
 @ContextConfiguration(
         locations = {
                 "classpath*:/META-INF/net.shibboleth.idp/postconfig.xml",
-                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication.xml", 
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication-reject-policy.xml", 
                 "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
-                TestWebAuthn2FaApplicationContextInitializer.class
+                TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer.class
                 }
         )
-public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
+/* The policy is set to REJECT.*/
+public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
     
     /** Flow ID. */
     @Nonnull public static final String FLOW_ID = "authn/WebAuthn";
@@ -62,27 +57,38 @@ public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
     /**
      * Constructor.
      */
-    protected TestSecondFactorFlow() {
+    protected TestPasswordlessFlowWithPolicy() {
         super(FLOW_ID);
     }
     
+    /* Should end on username collection when starting the flow.*/
     @SuppressWarnings("null")
     @Test
-    public void testSecondFactorFlow() throws Exception {
+    public void testPasswordlessFlow_WithPolicy() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
                 createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
-        buildMfaContext(prc.ensureSubcontext(AuthenticationContext.class), "authn/Password");
 
         final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
                 addToConversationScopeMap(Map.of("opensamlProfileRequestContext", prc)));
 
+        assertFlowExecutionActive(result.getSecond());
+        assertCurrentStateEquals("CollectUsernameView", result.getSecond());
+        
+        // Do next part of flow, populate the username
+        ExternalContextHolder.setExternalContext(externalContext);
+        setHttpFormRequest("POST", Map.of("j_username", USERNAME));
+        externalContext.setEventId("proceed");
+        
+        result.getSecond().setCurrentState("CollectUsernameView");
+        result.getSecond().resume(externalContext);
+        
         assertFlowExecutionActive(result.getSecond());
         assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
-        assertPublicKeyCredentialRequestOptions(prc, true, false);
+        assertPublicKeyCredentialRequestOptions(prc, true, true);
         
         // Do assertion validation half of flow
         
@@ -93,7 +99,7 @@ public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -105,45 +111,68 @@ public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
         result.getSecond().setCurrentState("DisplayWebAuthnView");
         result.getSecond().resume(externalContext);
         
-        // assert end conditions, assume existing username principal from the faked password flow
-        assertEquals(result.getSecond().getOutcome().getId(), "proceed");
-        assertAuthenticationSuccessConditions(prc, false);
+        // Policy failure will return the user to the webauthn login page
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
         
     }
     
     /* 
-     * The flow will proceed as if first factor passwordless, as no acceptable previous result. So we will end up on the 
-     * CollectUsernameView instead of DisplayWebAuthnView.
+     * Tests the passwordless flow when there is no userHandle in the assertion response. Which is allowable if
+     * a set of 'allowedCredentials' are supplied to the WebAuthn get request. Policy is set to reject, so it should
+     * be rejected.
      */
     @SuppressWarnings("null")
     @Test
-    public void testSecondFactorFlow_WrongPreviousResult() throws Exception {
+    public void testPasswordlessFlow_WithPolicy_NoUserHandleInAssertion() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
                 createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
-        buildMfaContext(prc.ensureSubcontext(AuthenticationContext.class), "authn/Wrong");
 
         final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
                 addToConversationScopeMap(Map.of("opensamlProfileRequestContext", prc)));
 
         assertFlowExecutionActive(result.getSecond());
-        // Stop here, because we are not testing the passwordless flow that we are now in.
         assertCurrentStateEquals("CollectUsernameView", result.getSecond());
         
+        // Do next part of flow, populate the username
+        ExternalContextHolder.setExternalContext(externalContext);
+        setHttpFormRequest("POST", Map.of("j_username", USERNAME));
+        externalContext.setEventId("proceed");
         
+        result.getSecond().setCurrentState("CollectUsernameView");
+        result.getSecond().resume(externalContext);
         
-    }
-    
-    private void buildMfaContext(final AuthenticationContext ac, final String previousFlow) {
+        assertFlowExecutionActive(result.getSecond());
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        assertPublicKeyCredentialRequestOptions(prc, true, true);
+        
+        // Do assertion validation half of flow
+        
+        // Get the challenge that was set into the PublicKeyCredentialRequestOptions. 
+        // otherwise we will end up signing a different one, which will provide its own test.
+        final WebAuthnAuthenticationContext authnContext = getWebAuthnAuthenticationContext(prc);
+        final ByteArray challenge = authnContext.getPublicKeyCredentialRequestOptions().getChallenge();
+        
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+        assertionResponse = createAssertionReponseFrom(
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), true);
+
+        final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
+        
+        // Re-set external context to holder
+        ExternalContextHolder.setExternalContext(externalContext);
+        setHttpFormRequest("POST", Map.of(ExtractPublicKeyCredentialAssertionFromFormRequest.DEFAULT_PARAMETER_NAME, 
+                assertionResponseJson));
+        externalContext.setEventId("proceed");
+        result.getSecond().setCurrentState("DisplayWebAuthnView");
+        result.getSecond().resume(externalContext);
+        
+        // Policy failure will return the user to the webauthn login page
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
         
-        final MultiFactorAuthenticationContext mfaCtx = new MultiFactorAuthenticationContext();
-        // Create a fake password flow result
-        final AuthenticationResult result = new AuthenticationResult("password", new Subject());        
-        mfaCtx.getActiveResults().put(previousFlow, result);
-        ac.addSubcontext(mfaCtx);
     }
 
 }
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
index aef5fc4..916845d 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestRegistrationFlow.java
@@ -320,7 +320,7 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlow.java
index e230574..be9dfc6 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlow.java
@@ -93,7 +93,7 @@ public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java
index 36db8d0..a20c4ad 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java
@@ -90,7 +90,7 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -148,7 +148,7 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -195,7 +195,7 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         mockAuthenticator.setUVNotPerformed(true);
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>        
             assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -242,7 +242,7 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         mockAuthenticator.setProduceBadAssertionSignatures(true);  
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -286,7 +286,8 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), "wrong.origin.com");
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), "wrong.origin.com"
+                , false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -394,7 +395,7 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -434,7 +435,7 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         // Generate a challenge which is not the same as the one generated by the flow
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), generateRandomBytes(32));
+                registration.getCredential().getCredentialId().getBytes(), generateRandomBytes(32), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -477,7 +478,7 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlowWithPolicy.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlowWithPolicy.java
index 511c082..93936cd 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlowWithPolicy.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlowWithPolicy.java
@@ -64,7 +64,7 @@ public class TestUsernameslessFlowWithPolicy extends AbstractWebAuthnFlowTest{
      * initializer.*/
     @SuppressWarnings("null")
     @Test
-    public void testUsernamelessFlow_CredentialRejected_2FAOnly() throws Exception {
+    public void testUsernamelessFlow_WithPolicy_CredentialRejected_2FAOnly() throws Exception {
         //Register a credential for use that is only suitable for 2FA
         final CredentialRecord registration = 
                 createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
@@ -88,7 +88,7 @@ public class TestUsernameslessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer.java
new file mode 100644
index 0000000..ea383b3
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.flow;
+
+import javax.annotation.Nonnull;
+
+import org.slf4j.Logger;
+import org.springframework.context.ApplicationContextInitializer;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.mock.env.MockPropertySource;
+
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An {@link ApplicationContextInitializer} which prepends properties.
+ *
+ * <p>This needs to include the original IdP-test-layer properties and has to be
+ * set to {@link Ordered#LOWEST_PRECEDENCE} or things blow up.</p>
+ */
+ at Order(Ordered.LOWEST_PRECEDENCE)
+public class TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer
+        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer.class);
+
+    /** {@inheritDoc} */
+    @Override public void initialize(@Nonnull final ConfigurableApplicationContext applicationContext) {
+        final MockPropertySource mock = new MockPropertySource("passwordless-mock-properties");
+        mock.setProperty("idp.authn.webauthn.usernameless.enabled", "false");
+        mock.setProperty("idp.authn.webauthn.credential.policy.enabled", "true");
+        applicationContext.getEnvironment().getPropertySources().addFirst(mock);
+        log.info("Prepending usernameless properties '{}'", mock.getSource());
+    }
+    
+}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
index 36380e6..792c5a0 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnTest.java
@@ -410,7 +410,7 @@ public abstract class AbstractWebAuthnTest {
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
        return assertion;
     }
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckCredentialPolicyTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckCredentialPolicyTest.java
new file mode 100644
index 0000000..b229ade
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckCredentialPolicyTest.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.impl;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertNull;
+
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeSet;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.yubico.webauthn.RegisteredCredential;
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.AuthenticatorTransport;
+import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
+import com.yubico.webauthn.data.UserIdentity;
+
+import net.shibboleth.idp.plugin.authn.webauthn.authn.WebAuthnAuthenticationEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * Tests for {@link CheckCredentialPolicy}.
+ */
+public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
+    
+    /** The action to test.*/
+    private CheckCredentialPolicy action;
+    
+    /** The WebAuthn context.*/
+    private WebAuthnAuthenticationContext context;
+    
+    /** The stashed attestation.*/
+    private PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation;
+    
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        context = addWebAuthnAuthenticationContext();
+        
+        action = new CheckCredentialPolicy();
+        action.setWebAuthnClient(client);
+        action.setCredentialRepository(credentialRepo);
+        
+        mockAuthenticator = new MockAuthenticator(RPID);
+        
+        final var user = UserIdentity.builder()
+                .name("jdoe")
+                .displayName("John Doe")
+                .id(ByteArray.fromBase64(USER_HANDLE_B64))
+                .build();
+       
+        
+        attestation = createAttestationReponse();
+        
+        final var uHandleArray = ByteArray.fromBase64(USER_HANDLE_B64);
+        assert uHandleArray.getBase64().equals(USER_HANDLE_B64);
+        assert Arrays.equals(uHandleArray.getBytes(), Base64Support.decode(USER_HANDLE_B64));
+
+        
+        final RegisteredCredential credential = RegisteredCredential.builder()
+                .credentialId(attestation.getId())
+                .userHandle(ByteArray.fromBase64(USER_HANDLE_B64))
+                .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+                        .getAttestedCredentialData().get().getCredentialPublicKey())
+                .build();
+         
+         final var reg = CredentialRecord.builder()
+                 .withUserIdentity(user)
+                 .withUsername(USERNAME)
+                 .withTransports(new TreeSet<AuthenticatorTransport>())
+                 .withRegistrationTime(Instant.now())
+                 .withCredential(credential)
+                 .withCredentialNickname("nickname")
+                 .withDiscoverable(Optional.of(Boolean.TRUE))
+                 .withAaguid(attestation.getResponse().getParsedAuthenticatorData()
+                         .getAttestedCredentialData().get().getAaguid().getBytes())
+                 .withUserVerified(true)
+                 .build();
+  
+        
+        credentialRepo.addRegistrationByUsername(USERNAME, reg);  
+        context.setExistingCredentials(CollectionSupport.setOf(
+                new EnhancedCredentialRecord(reg)));
+        context.setUserId(uHandleArray.getBytes());
+        context.setUsername(USERNAME);
+        
+    } 
+    
+    @Test
+    public void testCredentialPolicy_Accept() throws Exception {
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Dummy Accept Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.ACCEPT;
+            }
+            
+        });
+        action.initialize();
+        
+        final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64);         
+
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                    clientDataGet, null, false);
+        
+        //Set the assertion (authentication) response based on the credential we've already registered
+        context.setPublicKeyCredentialAssertionResponse(assertion);
+        
+        final Event event = action.execute(src);
+        assertNull(event);
+    }
+    
+    @Test
+    public void testCredentialPolicy_Rejected() throws Exception {
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Dummy Reject Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.REJECT;
+            }
+            
+        });
+        action.initialize();
+        
+        final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64);         
+
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                    clientDataGet, null, false);
+        
+        //Set the assertion (authentication) response based on the credential we've already registered
+        context.setPublicKeyCredentialAssertionResponse(assertion);
+        
+        final Event event = action.execute(src);
+        assertNotNull(event);
+        assert event != null;
+        assertEquals(event.getId(),  WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+    }
+    
+    /* Should be evaluated with or without a userHandle in the response. Passwordless and 2FA responses do not need
+     * to contain a userHandle, the IdP already knows it.*/
+    @Test
+    public void testCredentialPolicy_NoUserHandle() throws Exception {
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Dummy Reject Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.REJECT;
+            }
+            
+        });
+        action.initialize();
+        
+        final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64);         
+
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                    clientDataGet, null, true);
+        
+        //Set the assertion (authentication) response based on the credential we've already registered
+        context.setPublicKeyCredentialAssertionResponse(assertion);
+        
+        final Event event = action.execute(src);
+        assert event != null;
+        assertEquals(event.getId(),  WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+    }
+    
+    /* Policy can not run because userHandle is not present in the assertion and userId is not present in the context.*/
+    @Test
+    public void testCredentialPolicy_NoUserHandle_NoUserId() throws Exception {
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Dummy Reject Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.REJECT;
+            }
+            
+        });
+        action.initialize();
+        
+        final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64);         
+
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                    clientDataGet, null, true);
+        
+        //Set the assertion (authentication) response based on the credential we've already registered
+        context.setPublicKeyCredentialAssertionResponse(assertion);
+        
+        
+        final Event event = action.execute(src);
+        assert event != null;
+        assertEquals(event.getId(),  WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+    }
+
+}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java
index 1b6888a..5ea3e78 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandleTest.java
@@ -109,7 +109,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
         webAuthnContext.setPublicKeyCredentialAssertionResponse(assertion);
         
@@ -164,7 +164,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
         webAuthnContext.setPublicKeyCredentialAssertionResponse(assertion);
         
@@ -193,7 +193,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
         webAuthnContext.setPublicKeyCredentialAssertionResponse(assertion);
         
@@ -223,7 +223,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Now generate an assertion (authentication) and check it is valid
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
         webAuthnContext.setPublicKeyCredentialAssertionResponse(assertion);
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java
index a5134ea..e0d7d04 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertionTest.java
@@ -102,7 +102,7 @@ public class ValidateWebAuthnAssertionTest extends AbstractWebAuthnTest {
 
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
         //Set the assertion (authentication) response based on the credential we've already registered
         context.setPublicKeyCredentialAssertionResponse(assertion);
@@ -123,7 +123,7 @@ public class ValidateWebAuthnAssertionTest extends AbstractWebAuthnTest {
 
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
             assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
-                    clientDataGet, null);
+                    clientDataGet, null, false);
         
         //Set the assertion (authentication) response based on the credential we've already registered
         context.setPublicKeyCredentialAssertionResponse(assertion);
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/RejectAllCredentialPolicyRule.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/RejectAllCredentialPolicyRule.java
new file mode 100644
index 0000000..3600f2c
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/RejectAllCredentialPolicyRule.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.authn.webauthn.policy.impl;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+
+/**
+ * A simple mock policy that REJECTS any credential.
+ */
+public class RejectAllCredentialPolicyRule extends AbstractCredentialPolicyRule{
+
+    /** {@inheritDoc} */
+    @Override
+    protected CredentialPolicyOutcome doEvaluate(final EnhancedCredentialRecord credential, 
+            final ProfileRequestContext prc,
+            final WebAuthnAuthenticationContext webAuthnContext) {
+        return CredentialPolicyOutcome.REJECT;
+    }
+
+}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java
index fd9f8eb..01dc0b1 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java
@@ -74,8 +74,8 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * 
  * <p>A new credential is created and stored for every call to 
  * {@link #createAuthenticatorAttestationResponse(String, Map, byte[])}. Calls to 
- * {@link #createAuthenticatorAssertionResponse(String, byte[], Map)} require the credentialId so it can use information
- * stored when the credential was created. Hence to use {@link #createAuthenticatorAssertionResponse(String, byte[], Map)}
+ * {@link #createAuthenticatorAssertionResponse(String, byte[], Map, boolean)} require the credentialId so it can use information
+ * stored when the credential was created. Hence to use {@link #createAuthenticatorAssertionResponse(String, byte[], Map, boolean)}
  * you must first call {@link #createAuthenticatorAttestationResponse(String, Map, byte[])}.</p>
  * 
  * <p> Does not support the attestation statement 'attStmt' (which provides data provenance information for the public 
@@ -236,13 +236,14 @@ public class MockAuthenticator {
      * @param clientData the client data passed by the 'client' (browser) during registration.
      * @param overrideUserHandle if set, override the userHandle in the assertion response. Normally the userHandle 
      *      comes from what was stored in the attestation when the key was created. 
+     * @param blankUserHandle TODO
      * @return the public key credential assertion
      * 
      * @throws Exception on error
      */
     public com.yubico.webauthn.data.PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
         createAuthenticatorAssertionResponse(@Nonnull final byte[] credentialId, final Map<String, String> clientData, 
-                final byte[] overrideUserHandle) throws Exception {
+                final byte[] overrideUserHandle, final boolean blankUserHandle) throws Exception {
         
         final var credentialb64 = Base64Support.encodeURLSafe(credentialId);
         final var publicKeyAttestation = createdCredentialsMaps.get(credentialb64);
@@ -258,8 +259,11 @@ public class MockAuthenticator {
                 sign(authenticatorData, clientDataCompactSerialization, 
                         publicKeyAttestation.getResponse().getPrivateKey());
         
-        final var userHandle = overrideUserHandle != null ? overrideUserHandle : 
+        var userHandle = overrideUserHandle != null ? overrideUserHandle : 
             publicKeyAttestation.getResponse().getUserHandle();
+        if (blankUserHandle) {
+            userHandle = null;
+        }
         final var pkCred = new PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs>(rawCredentialIdentifier,
                 new Assertion(clientDataCompactSerialization.getBytes(), authenticatorData, signature, userHandle),
                 new AuthenticatonExtensionsClientOutputs());
diff --git a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication-reject-policy.xml b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication-reject-policy.xml
new file mode 100644
index 0000000..6b29aa0
--- /dev/null
+++ b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication-reject-policy.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:context="http://www.springframework.org/schema/context"
+       xmlns:util="http://www.springframework.org/schema/util"
+       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
+       xmlns:p="http://www.springframework.org/schema/p"
+       xmlns:c="http://www.springframework.org/schema/c"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+                           http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
+                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+                           
+       default-init-method="initialize"
+       default-destroy-method="destroy">
+       
+    <bean id="shibboleth.authn.WebAuthn.AbstractCredentialPolicyRule" scope="prototype" abstract="true"
+        p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
+  
+
+    <!-- 
+    The default list to add credential policies used during authentication. The property 
+    idp.authn.webauthn.credential.policy.enabled must be set to true for these to take effect.
+    
+    -->
+     <util:list id="shibboleth.authn.WebAuthn.ChainedCredentialPolicyList">
+        <bean id="RejectAllCredentialPolicy" parent="shibboleth.authn.WebAuthn.AbstractCredentialPolicyRule"
+                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.RejectAllCredentialPolicyRule"/>
+    </util:list>
+    
+</beans>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list