[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-43 - evaluate credential policy rules for all tokens in store before authentication

Phil Smart philip.smart at jisc.ac.uk
Fri Feb 7 14:24:19 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=5b27b99d2d00de5f8fa3ee17c6a6dc6029b91dfa

The following commit(s) were added to refs/heads/main by this push:
     new 5b27b99  JWEBAUTHN-43 - evaluate credential policy rules for all tokens in store before authentication
5b27b99 is described below

commit 5b27b99d2d00de5f8fa3ee17c6a6dc6029b91dfa
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Feb 7 14:24:16 2025 +0000

    JWEBAUTHN-43 - evaluate credential policy rules for all tokens in store
    before authentication
    
     - Add a filter policy to both passwordless and 2fa flows. That is, were
    the user and their credentials are identified in advance of calling the
    WebAuthn API.
     - Signal a customisable non-proceed event if the user's credentials are
    all filtered by the policy. Otherwise the WebAuthn would not see a list
    of allowedCredentials and present all of them for selection (assumed
    usernameless authentication).
     - Add lots more tests to make sure there is not a bypass from doing
    this.
     - Some minor nits and cleanups
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-43
---
 .../client/WebAuthnAuthenticationClient.java       |   6 +-
 .../impl/YubicoWebAuthnAuthenticationClient.java   |   3 +-
 .../authn/webauthn/impl/CheckCredentialPolicy.java |  93 ++------
 .../impl/CheckUserHandleAgainstUsername.java       | 118 ++++++++++
 .../CheckUserHandleExistsIfNoAllowCredentials.java | 118 ++++++++++
 .../webauthn/impl/FilterCredentialsOnPolicy.java   | 148 ++++++++++++
 .../LookupRegisteredCredentialsFromUserHandle.java |   6 +-
 .../webauthn/impl/ValidateWebAuthnAssertion.java   |   4 +-
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |  15 +-
 .../idp/flows/authn/WebAuthn/webauthn-flow.xml     |  24 +-
 .../authn/webauthn/conf/authn/webauthn.properties  |   9 +-
 .../YubicoWebauthnAuthenticationClientTest.java    |  14 +-
 .../webauthn/flow/AbstractWebAuthnFlowTest.java    |  30 ++-
 .../webauthn/flow/TestAdminManagementFlow.java     |   2 +-
 .../authn/webauthn/flow/TestPasswordlessFlow.java  | 253 ++++++++++++++++++++-
 ...a => TestPasswordlessFlowWithFilterPolicy.java} | 106 +++++----
 .../flow/TestPasswordlessFlowWithPolicy.java       | 179 ++++++++++++++-
 .../authn/webauthn/flow/TestRegistrationFlow.java  |  16 +-
 .../authn/webauthn/flow/TestSecondFactorFlow.java  |  54 ++++-
 ...a => TestSecondFactorFlowWithFilterPolicy.java} | 122 ++++++----
 .../authn/webauthn/flow/TestUsernameslessFlow.java | 176 ++++++++++----
 .../flow/TestUsernameslessFlowWithPolicy.java      |  77 ++++++-
 ...hFilterPolicyApplicationContextInitializer.java |  51 +++++
 ...hFilterPolicyApplicationContextInitializer.java |  50 ++++
 .../authn/webauthn/impl/AbstractWebAuthnTest.java  |   4 +-
 .../webauthn/impl/CheckCredentialPolicyTest.java   |  55 ++---
 .../impl/FilterCredentialsOnPolicyTest.java        | 210 +++++++++++++++++
 ...kupRegisteredCredentialsFromUserHandleTest.java |  10 +-
 .../impl/LookupRegisteredCredentialsTest.java      |   2 +-
 .../impl/ValidateWebAuthnAssertionTest.java        |   2 +-
 .../policy/impl/RejectOneCredentialPolicyRule.java |  57 +++++
 .../impl/CredentialRegistrationSerializerTest.java |   2 +-
 ...IdPStorageServiceCredentialRespositoryTest.java |   3 +-
 .../webauthn/storage/impl/MockAuthenticator.java   |   7 +-
 .../test-beans-authentication-reject-policy.xml    |   7 +-
 35 files changed, 1717 insertions(+), 316 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/WebAuthnAuthenticationClient.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/WebAuthnAuthenticationClient.java
index 740c694..fb6dfa5 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/WebAuthnAuthenticationClient.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/WebAuthnAuthenticationClient.java
@@ -77,8 +77,10 @@ public interface WebAuthnAuthenticationClient {
      /**
       * Validate the PublicKeyCredential authenticator assertion response from an authentication request.
       * 
-      * @param username the username of the users account on the IdP. Should map one-to-one with the user.id.
-      * @param userId the user.id of the users account on the IdP. Should match the userHandle in the assertion response
+      * @param username the username of the users account on the IdP as set in the assertion request. 
+      *                 Should map one-to-one with the user.id.
+      * @param userId the user.id of the users account on the IdP as set in the assertion request. 
+      *                 Should match the userHandle in the assertion response
       * @param publicKeyCredentialRequestOptions the options used when generating an assertion for authentication.
       * @param publicKeyCredentialAssertionResponse the assertion response.
       * 
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java
index 3fcfcbf..e64b741 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebAuthnAuthenticationClient.java
@@ -175,7 +175,8 @@ public class YubicoWebAuthnAuthenticationClient implements WebAuthnAuthenticatio
                     log.debug("Attempting validation of assumed discoverable credential with userHandle from response "
                             + "'{}'", userHandleBase64);
                 } else {
-                    log.debug("Attempting validation of credential with username '{}' and userHandle '{}'",
+                    log.debug("Attempting validation of credential '{}' with request username '{}' and request "
+                            + "userHandle '{}'",authenticatorAssertionResponse.getId().getBase64(),
                             username, WebAuthnSupport.toBase64OrUnknown(userId));
                 }
             }
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 c8a3f86..3f0b10b 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
@@ -17,7 +17,6 @@ package net.shibboleth.idp.plugin.authn.webauthn.impl;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Function;
-import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -46,17 +45,15 @@ 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.logic.PredicateSupport;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * A policy engine action that checks with the configured policy if the credential, used to sign the assertion, can 
+ * A policy engine action that checks with the configured policy if the credential, used to sign the assertion can 
  * be used to authenticate. If it is rejected by the policy, a 
- * {@link WebAuthnAuthenticationEventIds#CREDENTIAL_POLICY_REJECTION} is returned. If #rejectIfNoCredentialsFound is 
- * true, and the credential used to sign the assertion can not be found from the credential repository, a 
- * {@link WebAuthnAuthenticationEventIds#CREDENTIAL_POLICY_REJECTION} will be returned, else the policy is ignored. 
+ * {@link WebAuthnAuthenticationEventIds#CREDENTIAL_POLICY_REJECTION} is returned. Similarly, if a credential can 
+ * not be found to evaluate the policy returns an {@link WebAuthnAuthenticationEventIds#CREDENTIAL_POLICY_REJECTION}. 
  * 
- * @event {EventIds#INVALID_PROFILE_CTX}\
+ * @event {EventIds#INVALID_PROFILE_CTX}
  * @event {WebAuthnAuthenticationEventIds#CREDENTIAL_POLICY_REJECTION}
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post the credential is allowed to be used for authentication, or an error event is triggered
@@ -73,12 +70,6 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
     /** Get the userHandle to help find the credential used to sign the assertion.*/ 
     @Nonnull private Function<ProfileRequestContext, byte[]> userHandleLookupStrategy;
     
-    /** 
-     * Reject the assertion if no credential can be found in the repository, otherwise the policy is ignored. 
-     * Defaults to false.
-     */
-    @Nonnull private Predicate<ProfileRequestContext> rejectIfNoCredentialFound;
-    
     /** The credential policy to check.*/
     @Nullable private CredentialPolicy credentialPolicy;
     
@@ -95,7 +86,6 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
         super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
                 compose(new ChildContextLookup<>(AuthenticationContext.class)));
         userHandleLookupStrategy = new DefaultUserHandleLookupStrategy();
-        rejectIfNoCredentialFound = PredicateSupport.alwaysFalse();
     }
     
     /**
@@ -122,30 +112,6 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
                 "UserHandle lookup strategy can not be null");
     }
     
-    /**
-     * Set the predicate to determine if finding no credential to test should result in a policy rejection.
-     * 
-     * @param predicate The predicate to set.
-     * 
-     * @since 1.1.0
-     */
-    public void setRejectIfNoCredentialFoundPredicate(final Predicate<ProfileRequestContext> predicate) {
-        checkSetterPreconditions();
-        rejectIfNoCredentialFound = Constraint.isNotNull(predicate,
-                "rejectIfNoCredentialsFound can not be null");
-    }
-    
-    /**
-     * Set the flag to determine if finding no credential to test should result in a policy rejection.
-     * 
-     * @param flag The flag to set.
-     * 
-     * @since 1.1.0
-     */
-    public void setRejectIfNoCredentialFound(final boolean flag) {
-        checkSetterPreconditions();
-        rejectIfNoCredentialFound = flag ? PredicateSupport.alwaysTrue() : PredicateSupport.alwaysFalse();
-    }
     
     /** {@inheritDoc} */
     @Override
@@ -195,16 +161,10 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
         
         final byte[] userHandleBytes = userHandleLookupStrategy.apply(profileRequestContext);      
         if (userHandleBytes == null) {
-            log.debug("{} UserHandle could not be found, policy can not be applied",getLogPrefix());
-            if (!rejectIfNoCredentialFound.test(profileRequestContext)) {                
-                return;
-            } else {
-                authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
-                        WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
-                ActionSupport.buildEvent(profileRequestContext,
-                        WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
-                return;
-            }
+            log.trace("{} UserHandle could not be found, policy rejected credential",getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext,
+                    WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+            return;
         }
         
         final Collection<EnhancedCredentialRecord> registeredCredentials = context.getExistingCredentials();
@@ -217,34 +177,25 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
         }).toList();
 
         if (credentials.isEmpty()) {
-            log.trace("{} UserHandle '{}' does not have a registered credential, policy can not be applied",getLogPrefix(), 
-                    userHandle.getBase64());
-            if (!rejectIfNoCredentialFound.test(profileRequestContext)) {                
-                return;
-            } else {
-                authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
-                        WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
-                ActionSupport.buildEvent(profileRequestContext,
-                        WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
-                return;
-            }             
+            log.trace("{} UserHandle '{}' does not have a registered credential, policy rejected credential",
+                    getLogPrefix(), userHandle.getBase64()); 
+            authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
+                    WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+            ActionSupport.buildEvent(profileRequestContext,
+                    WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+            return;           
         }
         
         // Reject if more than one credential that matches the credentialID and userHandle in the assertion. Should
         // never happen.
         if (credentials.size() != 1) {
-            log.debug("{} Credential ID '{}' for userHandle '{}' has more than one registered credential, "
+            log.trace("{} Credential ID '{}' for userHandle '{}' has more than one registered credential, "
                     + "policy can not be applied, rejecting",getLogPrefix(), assertion.getId(),
-                    userHandle.getHex());           
-            if (!rejectIfNoCredentialFound.test(profileRequestContext)) {                
-                return;
-            } else {
-                authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
-                        WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
-                ActionSupport.buildEvent(profileRequestContext,
-                        WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
-                return;
-            } 
+                    userHandle.getHex());
+            authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
+                    WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+            ActionSupport.buildEvent(profileRequestContext, WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
+            return;
         }
         
         final EnhancedCredentialRecord credentialToEvaluate = credentials.get(0);
@@ -252,7 +203,7 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
         final CredentialPolicyOutcome outcome = localPolicy.evaluate(credentialToEvaluate, profileRequestContext);
         
         if (outcome == CredentialPolicyOutcome.REJECT) {
-            log.warn("{} CredentialPolicy '{}' has rejected credential '{}'", getLogPrefix(), localPolicy.getId(),
+            log.trace("{} CredentialPolicy '{}' has rejected credential '{}'", getLogPrefix(), localPolicy.getId(),
                     credentialToEvaluate.getCredentialRecord().getCredentialIdBase64());
             authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
                     WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckUserHandleAgainstUsername.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckUserHandleAgainstUsername.java
new file mode 100644
index 0000000..e05e00e
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckUserHandleAgainstUsername.java
@@ -0,0 +1,118 @@
+/*
+ * 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.Optional;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An action that checks if the userHandle in the authentication assertion refers to the same user as in the WebAuthn 
+ * context. Noting, under some circumstances e.g. a passwordless flow where the user and their credentials have 
+ * already been identified, the authenticator does not need to provide a userHandle, in which case this action does 
+ * nothing.
+ * 
+ * 
+ * @event {EventIds#INVALID_PROFILE_CTX}
+ * @event {AuthnEventIds#INVALID_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
+ * @post a non-proceed event is signalled if the username found for the userHandle does not match that in the context
+ * 
+ * @since 1.1.0
+ */
+public class CheckUserHandleAgainstUsername extends AbstractWebAuthnAction<WebAuthnAuthenticationContext> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CheckUserHandleAgainstUsername.class);
+    
+    /** The credential repository to use.*/
+    @NonnullAfterInit private WebAuthnCredentialRepository repository;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }
+    
+    /**
+     * Constructor.
+     */
+    protected CheckUserHandleAgainstUsername() {
+        super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final WebAuthnAuthenticationContext context) {
+        
+       
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion = 
+                context.getPublicKeyCredentialAssertionResponse();
+        if (assertion == null) {
+            log.error("{} Unable to find Assertion in WebAuthn authentication context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return;
+        }
+        final String username = context.getUsername();
+        
+        final Optional<ByteArray> userHandle = assertion.getResponse().getUserHandle();
+        if (userHandle.isEmpty()) {
+            log.trace("{} User could not be found, the authenticator did not supply a userHandle", getLogPrefix());
+        } else {
+            final Optional<String> potentialUsername = repository.getUsernameForUserHandle(userHandle.get());
+            
+            if (potentialUsername.isEmpty()) {
+                log.trace("{} User could not be found from the supplied userHandle, no registered credentials", 
+                        getLogPrefix());
+            } else if (username != null && !potentialUsername.get().equals(username)){
+                 
+                log.trace("{} Username '{}' found from the userHandle was not the same as in the authentication "
+                        + "context '{}'",getLogPrefix(), potentialUsername.get(), username);
+                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
+                return;                               
+                
+            } else {
+                log.trace("{} Username '{}' found from the userHandle matched that in the authentication context",
+                        getLogPrefix(), potentialUsername.get());
+            }
+        }
+             
+    } 
+
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckUserHandleExistsIfNoAllowCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckUserHandleExistsIfNoAllowCredentials.java
new file mode 100644
index 0000000..2bb2530
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CheckUserHandleExistsIfNoAllowCredentials.java
@@ -0,0 +1,118 @@
+/*
+ * 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.List;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
+import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * An action which checks if the userHandle is returned in the assertion response if the set of allowedCredentials
+ * in the request was empty. If they are empty, the userHandle must be returned, if they are not empty the userHandle
+ * may be returned in the response. 
+ * 
+ * @event {EventIds#INVALID_PROFILE_CTX}
+ * @event {AuthnEventIds#INVALID_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
+ * @post a non-proceed event occurs if the userHandle was not present when the set of allowed credentials is empty.
+ * 
+ * @since 1.1.0
+ */
+public class CheckUserHandleExistsIfNoAllowCredentials extends AbstractWebAuthnAction<WebAuthnAuthenticationContext> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CheckUserHandleExistsIfNoAllowCredentials.class);
+    
+    /** The stashed assertion from the context.*/
+    private PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion;
+
+    /** The stashed request options from the context.*/
+    private PublicKeyCredentialRequestOptions requestOptions;
+    
+    /**
+     * Constructor.
+     */
+    protected CheckUserHandleExistsIfNoAllowCredentials() {
+        super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(final ProfileRequestContext profileRequestContext, 
+            final WebAuthnAuthenticationContext context) {
+        
+        assertion = context.getPublicKeyCredentialAssertionResponse();
+        if (assertion == null) {
+            log.error("{} Unable to find Assertion in WebAuthn authentication context", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        requestOptions = context.getPublicKeyCredentialRequestOptions();
+        if (requestOptions == null) {
+            log.error("{} Unable to find credential request options in WebAuthn authentication context", 
+                    getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+            return false;
+        }
+        
+        
+        return true;
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final WebAuthnAuthenticationContext context) {
+        
+        final Optional<List<PublicKeyCredentialDescriptor>> allowCredentials = requestOptions.getAllowCredentials();
+        final Optional<ByteArray> userHandle = assertion.getResponse().getUserHandle();
+        
+        if (allowCredentials.isEmpty() || allowCredentials.get() == null || allowCredentials.get().isEmpty()) {            
+            if (userHandle.isEmpty()) {
+                log.debug("{} Allow credentials are empty and the userHandle was not returned in the response, the "
+                        + "userHandle is required in this case", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
+                return;
+            } else {
+                log.trace("{} Allow credentials are empty and the userHandle was returned in the response", 
+                        getLogPrefix());
+            }
+        } else {
+            log.trace("{} Allow credentials are not empty and the optional userHandle was {}", 
+                    getLogPrefix(), userHandle.isPresent() ? "returned in the response" :
+                            "not returned in the response");
+        }
+    } 
+
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/FilterCredentialsOnPolicy.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/FilterCredentialsOnPolicy.java
new file mode 100644
index 0000000..3abd6a3
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/FilterCredentialsOnPolicy.java
@@ -0,0 +1,148 @@
+/*
+ * 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.Collection;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+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.policy.CredentialPolicy.CredentialPolicyOutcome;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A policy engine that filters the set of existing credentials held in the context based on the policy.
+ * 
+ * @event {AuthnEventIds#INVALID_CREDENTIALS}
+ * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
+ * @post the set of credentials known for this user are filtered based on the policy configured
+ * 
+ * @since 1.1.0
+ */
+public class FilterCredentialsOnPolicy extends AbstractWebAuthnAction<WebAuthnAuthenticationContext> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(FilterCredentialsOnPolicy.class);
+    
+    /** The credential policy to check.*/
+    @Nullable private CredentialPolicy credentialPolicy;
+    
+    /** 
+     * The EventID of the event to build if no credentials remain after filtering.
+     */
+    @Nonnull @NotEmpty private String noCredentialsEventId;
+
+    /**
+     * Constructor.
+     */
+    protected FilterCredentialsOnPolicy() {
+        super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
+        noCredentialsEventId = AuthnEventIds.NO_CREDENTIALS;
+    }
+    
+    /**
+     * Set the EventID of the event to build if no credentials are found and {@code triggerEventOnNoCredentials} 
+     * is set.
+     * 
+     * @param eventId the eventId to build.
+     */
+    public void setNoCredentialsEventId(@Nonnull @NotEmpty final String eventId) {
+        checkSetterPreconditions();
+        noCredentialsEventId = Constraint.isNotEmpty(eventId, "NoCredentialsEventId can not be null or empty");
+    }
+    
+    /**
+     * Set the policy to verify that the credential can be used for authentication.
+     * 
+     * @param policy The authenticator policy to set.
+     */
+    public void setCredentialPolicy(@Nullable final CredentialPolicy policy) {
+        checkSetterPreconditions();
+        credentialPolicy = policy;
+    }
+    
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final WebAuthnAuthenticationContext context) {
+        
+        final CredentialPolicy localPolicy = credentialPolicy;
+        if (localPolicy == null) {
+            // If no policy, nothing can be applied
+            log.trace("{} No authenticator policy to apply", getLogPrefix());
+            return;
+        }
+        
+        final Collection<EnhancedCredentialRecord> existingCredentials = context.getExistingCredentials();
+
+        if (existingCredentials.isEmpty()) {
+            log.trace("{} User '{}' does not have any registered credentials, policy can not be applied",getLogPrefix(), 
+                    context.getUsername());
+            return;
+        }
+        
+        log.debug("{} User '{}' has '{}' credentials before policy filtering", getLogPrefix(), context.getUsername(), 
+                existingCredentials.size());
+        
+        final List<EnhancedCredentialRecord> filteredCredentials = 
+                existingCredentials.stream().filter(credentialToEvaluate -> {
+                final CredentialPolicyOutcome outcome = localPolicy.evaluate(credentialToEvaluate, profileRequestContext);
+                
+                if (outcome == CredentialPolicyOutcome.ACCEPT) {
+                    log.trace("{} CredentialPolicy '{}' has accepted credential '{}'", getLogPrefix(), 
+                            localPolicy.getId(), credentialToEvaluate.getCredentialRecord().getCredentialIdBase64());
+                    return true;
+                    
+                } else if (outcome == CredentialPolicyOutcome.REJECT) {
+                    log.trace("{} CredentialPolicy '{}' has rejected credential '{}', removing credential", 
+                            getLogPrefix(), localPolicy.getId(), 
+                            credentialToEvaluate.getCredentialRecord().getCredentialIdBase64()); 
+                    return false;
+                    
+                } else if (outcome == CredentialPolicyOutcome.IGNORE) {
+                    log.trace("{} CredentialPolicy '{}' was not active for credential '{}'", getLogPrefix(), 
+                            localPolicy.getId(), credentialToEvaluate.getCredentialRecord().getCredentialIdBase64());
+                    return true;
+                }
+                return true;
+        }).toList();
+        
+        context.setExistingCredentials(filteredCredentials);
+
+        log.debug("{} User '{}' has '{}' credentials after policy filtering", getLogPrefix(), context.getUsername(), 
+                filteredCredentials.size());
+        
+        if (filteredCredentials.isEmpty()) {
+            log.debug("{} All of '{}''s credentials have been filtered, triggering event '{}' ", 
+                    getLogPrefix(), context.getUsername(), noCredentialsEventId);            
+            ActionSupport.buildEvent(profileRequestContext, noCredentialsEventId);
+            return;
+        }
+             
+    } 
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
index a11a656..e6fde32 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsFromUserHandle.java
@@ -49,10 +49,10 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * response and sets them back onto the context. If no credentials exist and the trigger condition is set, an error 
  * event will be produced.
  * 
- * <p>Also ensures the username found from the userHandle matches that in the authentication context if supplied e.g.
- * for a passwordless authentication.</p>
+ * <p>Also ensures the username found from the userHandle matches that in the authentication context if supplied.</p>
  * 
  * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
+ * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
  * @post BaseWebAuthnContext.setExistingCredentials() is either null if no existing credentials are found, or contains
  * the credentials from the credential repository
  */
@@ -151,7 +151,7 @@ public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnA
                 if (username != null && !potentialUsername.get().equals(username)) {
                     log.debug("{} Username '{}' found from the userHandle was not the same as in the authentication "
                             + "context '{}'",getLogPrefix(), potentialUsername.get(), username);
-                    ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+                    ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
                     return;
                 }
                 
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
index a7274e3..a7e80ab 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
@@ -193,11 +193,11 @@ public class ValidateWebAuthnAssertion extends AbstractAuditingValidationAction
                     context.getUsername(), context.getUserId(), localPublicKeyCredentialRequestOptions, assertion);
             
             if (!result.isSuccess()) {
-                throw new AssertionFailureException("Assestion was not valid");
+                throw new AssertionFailureException("Assertion was not valid");
             }
             
             if (!result.isSignatureCounterValid()) {
-                throw new AssertionFailureException("Assestion was not valid, signature count is invalid");
+                throw new AssertionFailureException("Assertion was not valid, signature count is invalid");
             }
             if (updateSignatureCount.test(profileRequestContext)) {
                 // Update the signature count with that from the assertion. It has already been validated at this point
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
index 7b7f33f..e17202f 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-beans.xml
@@ -125,6 +125,12 @@
         p:noCredentialsEventId="%{idp.authn.webauthn.passwordless.noCredentialsEventId:NoRegisteredWebAuthnCredentials}"
         p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" />
 
+    <bean id="FilterCredentialsOnPolicy" scope="prototype" parent="AbstractWebAuthnAuthenticationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.FilterCredentialsOnPolicy"
+        p:activationCondition="%{idp.authn.webauthn.credential.filter.policy.enabled:false}"
+        p:credentialPolicy="#{getObject('%{idp.authn.webauthn.credential.filter.policy:shibboleth.authn.WebAuthn.ChainedCredentialPolicy}')}"
+        p:noCredentialsEventId="%{idp.authn.webauthn.credential.filter.policy.noCredentialsEventId:NoCredentials}"/>
+     
     <bean id="GenerateServerChallenge" scope="prototype" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
         p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" 
@@ -153,12 +159,19 @@
         p:triggerEventOnNoCredentials="%{idp.authn.webauthn.signalEventOnNoCredentialsRegisteredForUserHandle:false}"
         p:noCredentialsEventId="%{idp.authn.webauthn.userHandleNoRegisteredCredentialsEventId:NoCredentialsRegisteredForUserHandle}"/>    
 
+    <bean id="CheckUserHandleExistsIfNoAllowCredentials" scope="prototype" parent="AbstractWebAuthnAuthenticationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.CheckUserHandleExistsIfNoAllowCredentials"/> 
+        
+    <bean id="CheckUserHandleAgainstUsername" scope="prototype" parent="AbstractWebAuthnAuthenticationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.CheckUserHandleAgainstUsername"
+        p:credentialRepository="#{getObject('shibboleth.authn.WebAuthn.CredentialRepository') ?: getObject('shibboleth.authn.WebAuthn.DefaultCredentialRepository')}"
+        />
+    
     <bean id="CheckCredentialPolicy" parent="AbstractWebAuthnAuthenticationAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.CheckCredentialPolicy"
         p:credentialRepository="#{getObject('shibboleth.authn.WebAuthn.CredentialRepository') ?: getObject('shibboleth.authn.WebAuthn.DefaultCredentialRepository')}"
         p:credentialPolicy="#{getObject('%{idp.authn.webauthn.credential.policy:shibboleth.authn.WebAuthn.ChainedCredentialPolicy}')}"
         p:activationCondition="%{idp.authn.webauthn.credential.policy.enabled:false}"
-        p:rejectIfNoCredentialFound="%{idp.authn.webauthn.credential.policy.rejectIfNoCredentials:false}"
         p:userHandleLookupStrategy="#{getObject('shibboleth.authn.WebAuthn.CredentialPolicyUserHandleLookupStrategy')  ?: getObject('DefaultCredentialPolicyUserHandleLookupStrategy')}"/>
 
     <bean id="DefaultCredentialPolicyUserHandleLookupStrategy" scope="prototype" 
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
index 6468cfb..e316724 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
@@ -78,8 +78,10 @@
     
     <action-state id="PasswordlessLoginProceed">        
         <evaluate expression="LookupRegisteredCredentials"/>
+        <evaluate expression="FilterCredentialsOnPolicy"/>
         <evaluate expression="AddUserVerificationRequired"/>
         <evaluate expression="'proceed'" />
+
         <transition on="proceed" to="GenerateAuthenticationCeremonyOptions" />    
     </action-state>
     
@@ -100,8 +102,10 @@
     <action-state id="SecondFactorLogin">
         <evaluate expression="PopulateWebAuthnAuthenticationContextFor2FA"/>
         <evaluate expression="LookupRegisteredCredentials"/>
+        <evaluate expression="FilterCredentialsOnPolicy"/>
         <evaluate expression="AddUserVerificationNotRequired"/>
         <evaluate expression="'proceed'" />
+        
         <transition on="proceed" to="GenerateAuthenticationCeremonyOptions" />    
     </action-state>
     
@@ -163,23 +167,25 @@
     </decision-state>
 
     <action-state id="UsernamelessAssertion">
-        <!-- 
-        lookup credentials here so we can exit the process before validation if no registered credentials exist and
-        the plugin has been configured to trigger a custom event. Also set the known credentials onto the context
-        for the credential policies to run.
-        -->
+        <evaluate expression="CheckUserHandleExistsIfNoAllowCredentials"/>
         <evaluate expression="LookupRegisteredCredentialsFromUserHandle"/>
         <evaluate expression="'proceed'" />
         
         <transition on="proceed" to="ValidatePublicKeyCredential" />    
     </action-state>
     
-    <action-state id="SecondFactorAssertion">    
+    <action-state id="SecondFactorAssertion">  
+        <evaluate expression="CheckUserHandleExistsIfNoAllowCredentials"/>
+        <!-- TODO The WebAuthn client should also check the username it is given matches to the userHandle in the assertion response. So this is pre-emptive.-->
+        <evaluate expression="CheckUserHandleAgainstUsername"/>  
         <evaluate expression="'proceed'" />        
         <transition on="proceed" to="ValidatePublicKeyCredential" />    
     </action-state>
     
     <action-state id="PasswordlessAssertion">
+        <evaluate expression="CheckUserHandleExistsIfNoAllowCredentials"/>
+        <!-- TODO The WebAuthn client should also check the username it is given matches to the userHandle in the assertion response. So this is pre-emptive.-->
+        <evaluate expression="CheckUserHandleAgainstUsername"/>
         <evaluate expression="'proceed'" />        
         <transition on="proceed" to="ValidatePublicKeyCredential" />    
     </action-state>
@@ -190,8 +196,10 @@
         <evaluate expression="'proceed'" />
         
         <transition on="proceed" to="proceed" />
-        <!-- Import here we backtrack to a suitable action. For example, regenerate the challenge and options but
-        we do not need a new username input etc. -->
+        <!-- 
+        Important here we backtrack to a suitable action. For example, regenerate the challenge and options but
+        we do not need a new username.
+        -->
         <transition on="CredentialPolicyRejection" to="GenerateAuthenticationCeremonyOptions"/> 
         <!-- The default invalid credentials transition would be InvalidCredentials, configuring this event instead allows control to be returned to the view -->  
         <transition on="InvalidWebAuthnCredential" to="GenerateAuthenticationCeremonyOptions"/>           
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
index 162bea3..3efde48 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn.properties
@@ -64,10 +64,15 @@ idp.authn.webauthn.supportedPrincipals = \
 # The ID of the bean that supplies the c14n flows that are applied to the username entered during the passwordless flow
 #idp.authn.webauthn.passwordless.c14n.postUsernameFlows = shibboleth.PostLoginSubjectCanonicalizationFlows
 
+# Enable the credential filter policy engine for passwordless and second factor flows
+#idp.authn.webauthn.credential.filter.policy.enabled = false
+# Set the credential policies to use, defaults to a chained set of policies
+#idp.authn.webauthn.credential.filter.policy = shibboleth.authn.WebAuthn.ChainedCredentialPolicy
+# If there are no credentials left after filtering, the following non-proceed event is signalled
+#idp.authn.webauthn.credential.filter.policy.noCredentialsEventId = NoCredentials
+
 # Enable the credential policy engine
 #idp.authn.webauthn.credential.policy.enabled = false
-# Should the policy reject the credential used to sign the assertion if no credentials can be found in the repository for the user
-#idp.authn.webauthn.credential.policy.rejectIfNoCredentials = false
 # Set the credential policies to use, defaults to a chained set of policies
 #idp.authn.webauthn.credential.policy = shibboleth.authn.WebAuthn.ChainedCredentialPolicy
 # When using the default chained policy, which policy list should we use?
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 e2ba9c7..429452e 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
@@ -120,7 +120,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegistrationResult registration = 
                 client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
@@ -138,7 +138,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegistrationResult registration = 
                 client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
@@ -155,7 +155,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
            
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegistrationResult registration = 
                 client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
@@ -172,7 +172,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
@@ -246,7 +246,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
@@ -291,7 +291,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
@@ -337,7 +337,7 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
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 a5c4dde..f36c055 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
@@ -111,6 +111,9 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
     protected final static String USERNAME = "test-user";
     
     protected final static String DISPLAY_NAME = "test user";
+    
+    /** Base64 to represent the ID of a credential that will be filtered by the rejection policy".*/
+    protected final static String FILTERED_CREDENTIAL_ID = "ZmlsdGVyZWQtY3JlZGVudGlhbA==";
 
     protected String flowId;
     
@@ -210,12 +213,13 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
      * @param username the username
      * @param display name the display name
      * @param userHandleB64 the user handle base64 encoded
+     * @param credentialIdOverride the credential ID. Auto-generated if {@code null}.
      * @return the credential registration 
      * 
      * @throws Exception on error
      */
     protected CredentialRecord createCredentialRegistration(final String username, final String displayName,
-            final String userHandleB64) throws Exception {
+            final String userHandleB64, final byte[] credentialIdOverride) throws Exception {
 
         final var user = UserIdentity.builder()
                 .name(username)
@@ -225,7 +229,7 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
        
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
-                createAttestationReponse();
+                createAttestationReponse(userHandleB64, credentialIdOverride);
         
         final var barray = ByteArray.fromBase64(userHandleB64);
         assert barray.getBase64().equals(userHandleB64);
@@ -418,14 +422,18 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
     /**
      * Create a credential registration attestation response from the mock authenticator.
      * 
+     * @param userHandleB64 the userHandle base64 encoded
+     * @param credentialIdOverride the credential ID. Auto-generated if {@code null}.
+     * 
      * @return the credential registration 
      * 
      * @throws Exception on error
      */
     protected PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
-                createAttestationReponse() throws Exception {
+                createAttestationReponse(final String userHandleB64, final byte[] credentialIdOverride) 
+                        throws Exception {
         
-       return createAttestationReponse(Base64Support.decode(CHALLENGE_B64), USER_HANDLE_B64);
+       return createAttestationReponse(Base64Support.decode(CHALLENGE_B64), userHandleB64, credentialIdOverride);
     }
     
 
@@ -433,15 +441,16 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
      * Create a credential registration attestation response from the mock authenticator.
      * 
      * @param challenge the challenge
+     * @param credentialIdOverride the credential ID. Auto-generated if {@code null}.
      * 
      * @return the credential registration 
      * 
      * @throws Exception on error
      */
     protected PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
-                createAttestationReponse(final byte[] challenge) throws Exception {
+                createAttestationReponse(final byte[] challenge, final byte[] credentialIdOverride) throws Exception {
 
-        return createAttestationReponse(challenge, USER_HANDLE_B64);
+        return createAttestationReponse(challenge, USER_HANDLE_B64, credentialIdOverride);
     }
     
     /**
@@ -450,19 +459,22 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
      * @param challenge the challenge
      * @param userHandleB64 the userHandle to use. Note this is just stashed in the authenticator, the attestation 
      *                      response does not contain a userhandle.
+     * @param credentialIdOverride the credential ID. Auto-generated if {@code null}.
+     * 
      * @return the credential registration 
      * 
      * @throws Exception on error
      */
     protected PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>
-                createAttestationReponse(final byte[] challenge, final String userHandleB64) throws Exception {
+                createAttestationReponse(final byte[] challenge, final String userHandleB64,
+                        final byte[] credentialIdOverride) throws Exception {
 
         final String challengeB64 = Base64Support.encodeURLSafe(challenge);
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, challengeB64);
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(challengeB64, clientDataCreate, 
-                        Base64Support.decode(userHandleB64));
+                        Base64Support.decode(userHandleB64), credentialIdOverride);
         
        return attestation;
     }
@@ -479,7 +491,7 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
         
         //need to create on first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
-                    createAttestationReponse();
+                    createAttestationReponse(USER_HANDLE_B64, null);
         final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64); 
         
         // Now generate an assertion (authentication) and check it is valid
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 603a408..4bc2693 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
@@ -76,7 +76,7 @@ public class TestAdminManagementFlow extends AbstractWebAuthnFlowTest{
     public void testManagementFlow_RemoveCredential() throws Exception {
         //Register a credential for use and to delete.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
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 57e6bea..7844132 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
@@ -31,6 +31,7 @@ import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 
+import net.shibboleth.idp.authn.AuthnEventIds;
 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;
@@ -62,13 +63,27 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
         super(FLOW_ID);
     }
     
-    /* Should end on username collection when starting the flow.*/
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 1 credential</li>
+     * <li>Credential request options contains an allowCredentials with 1 credential</li>
+     * <li>User selects the credential from the provider, authenticator signs challenge</li>
+     * <li>UserHandle is returned, and the credential for that userHandle matches the username entered</li>
+     * <li>(Success) The WebAuthn client validates the credential</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testPasswordlessFlow() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64,null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
@@ -118,16 +133,168 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
         
     }
     
-    /* 
-     * 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.
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 0 credentials</li>
+     * <li>Credential request options contains an empty allowCredentials (like a usernameless flow)</li>
+     * <li>User selects a credential from the provider, authenticator signs challenge</li>
+     * <li>UserHandle is returned, but no credentials can be found to check against the username entered</li>
+     * <li>fail, on WebAuthn validation step 6, no credential</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
+    @Test
+    public void testPasswordlessFlow_UsernameHasNoCredentials() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        
+        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);
+        // Enter a user who has no credentials. 
+        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, false, 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(), 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);
+        
+        //Credential in assertion does not belong to the username entered. The user entered had no credentials.
+        assertEquals(result.getSecond().getOutcome().getId(), AuthnEventIds.INVALID_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
+        
+    }
+    
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 0 credentials</li>
+     * <li>Credential request options contains an empty allowCredentials (like a usernameless flow)</li>
+     * <li>User selects a different credential from the provider, authenticator signs challenge</li>
+     * <li>UserHandle is returned, and the credential for that userHandle does not match the username entered</li>
+     * <li>(Fail) the user.id for the username entered is not the same as the userHandle in the assertion.</li>
+     * </ol>
+     * 
+     * Note, In this scenario, it maybe reasonable to allow the user to select a credential they poses for a 
+     * different username than the one entered. But for now, be safe and block it.
+     * 
+     * 
+     * @throws Exception on error
+     */
+    @SuppressWarnings("null")
+    @Test
+    public void testPasswordlessFlow_UsernameFromUserHandleDoesNotMatchUsernameInContext() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        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);
+        // Add a username input here, which is different than the user that registered the credential
+        setHttpFormRequest("POST", Map.of("j_username", "different-username"));
+        externalContext.setEventId("proceed");
+        
+        result.getSecond().setCurrentState("CollectUsernameView");
+        result.getSecond().resume(externalContext);
+        
+        assertFlowExecutionActive(result.getSecond());
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        assertPublicKeyCredentialRequestOptions(prc, false, 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(), 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(), AuthnEventIds.INVALID_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
+        
+    }
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 1 credentials</li>
+     * <li>Credential request options contains an allowCredentials with 1 credential</li>
+     * <li>User selects the credential from the provider, authenticator signs challenge</li>
+     * <li>UserHandle is NOT returned, and the userHandle can not be checked against the username input</li>
+     * <li>(Success) the public key registration belongs to the user.id and username in the context</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
      */
     @SuppressWarnings("null")
     @Test
     public void testPasswordlessFlow_NoUserHandleInAssertion() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
@@ -176,5 +343,79 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
         assertAuthenticationSuccessConditions(prc, true);
         
     }
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 1 credentials</li>
+     * <li>Credential request options contains an allowCredentials with 1 credential</li>
+     * <li>User selects a different credential from the provider (maybe the user blanked out the allowCredentials in the
+     * WebAuthn request), authenticator signs challenge</li>
+     * <li>UserHandle is NOT returned, and the userHandle can not be checked against the username input</li>
+     * <li>fail, on WebAuthn validation step 5, unrequested credential</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
+    @SuppressWarnings("null")
+    @Test
+    public void testPasswordlessFlow_NoUserHandleInAssertion_UserChooseADifferentCredential() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        credentialRepo.addRegistrationByUsername(USERNAME, registration);
+        
+        final CredentialRecord differentRegistration = 
+                createCredentialRegistration("different-user", DISPLAY_NAME, "Cuxm3QTpyOdDlzTLDY9gFQ==", null);
+        credentialRepo.addRegistrationByUsername("different-user", differentRegistration);
+        
+        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();
+        
+        // The user managed to select a credential for another user. No UserHandle is returned.
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+        assertionResponse = createAssertionReponseFrom(
+                differentRegistration.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);
+        
+        assertEquals(result.getSecond().getOutcome().getId(), AuthnEventIds.INVALID_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
+        
+    }
 
 }
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithFilterPolicy.java
similarity index 65%
copy from webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
copy to webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithFilterPolicy.java
index f93b363..8850b48 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithFilterPolicy.java
@@ -14,6 +14,8 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.flow;
 
+import static org.testng.Assert.assertEquals;
+
 import java.util.Map;
 
 import javax.annotation.Nonnull;
@@ -29,14 +31,16 @@ import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 
+import net.shibboleth.idp.authn.AuthnEventIds;
 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;
+import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.collection.Pair;
 
 
 /**
- * Flow tests for the passwordless flow.
+ * Flow tests for the passwordless flow where the 'filter credentials after lookup' policy engine is active.
  */
 @ContextConfiguration(
         locations = {
@@ -45,11 +49,11 @@ import net.shibboleth.shared.collection.Pair;
                 "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
-                TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer.class
+                TestWebAuthnPasswordlessWithFilterPolicyApplicationContextInitializer.class
                 }
         )
 /* The policy is set to REJECT.*/
-public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
+public class TestPasswordlessFlowWithFilterPolicy extends AbstractWebAuthnFlowTest{
     
     /** Flow ID. */
     @Nonnull public static final String FLOW_ID = "authn/WebAuthn";
@@ -57,17 +61,33 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
     /**
      * Constructor.
      */
-    protected TestPasswordlessFlowWithPolicy() {
+    protected TestPasswordlessFlowWithFilterPolicy() {
         super(FLOW_ID);
     }
     
-    /* Should end on username collection when starting the flow.*/
+
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 1 credential</li>
+     * <li>Credentials are filtered. 0 remain.</li>
+     * <li>Credential request options contains an empty allowCredentials</li>
+     * <li>(Fail) as all credentials have been filtered</li>
+     * </ol>
+     * 
+     * <p>If we do not fail early, the user could select a credential that was filtered.</p>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
-    public void testPasswordlessFlow_WithPolicy() throws Exception {
+    public void testPasswordlessFlow_WithPolicy_AllFiltered() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, 
+                        Base64Support.decodeURLSafe(FILTERED_CREDENTIAL_ID));
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
@@ -86,49 +106,42 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         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(), 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);
-        
-        // Policy failure will return the user to the webauthn login page
-        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        //Fail if all credentials have been filtered
+        assertEquals(result.getSecond().getOutcome().getId(), AuthnEventIds.NO_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
         
     }
     
-    /* 
-     * 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.
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 2 credentialS</li>
+     * <li>Credentials are filtered. 1 remains.</li>
+     * <li>Credential request options contains an allowCredentials with 1 credential</li>
+     * <li>The user selects the other credential that was filtered (they modify the WebAuthn request).
+     * <li>No UserHandle in the response.</li>
+     * <li>(Fail) the filtered credential was used, and that had not been requested in the options, so the WebAuthn
+     * client check (step5) fails.</li>
+     * </ol>
+     * 
+     * 
+     * 
+     * @throws Exception on error
      */
     @SuppressWarnings("null")
     @Test
-    public void testPasswordlessFlow_WithPolicy_NoUserHandleInAssertion() throws Exception {
+    public void testPasswordlessFlow_WithPolicy_OneFiltered_SelectFilteredCredential_NoUserHandle() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
+        final CredentialRecord secondRegistration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, 
+                        Base64Support.decodeURLSafe(FILTERED_CREDENTIAL_ID));
+        credentialRepo.addRegistrationByUsername(USERNAME, secondRegistration);
+        
         final var prc = buildProfileRequestContext(false, false, USERNAME);
 
         final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
@@ -147,6 +160,7 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         
         assertFlowExecutionActive(result.getSecond());
         assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        // We expect all the credentials to be filtered by this point
         assertPublicKeyCredentialRequestOptions(prc, true, true);
         
         // Do assertion validation half of flow
@@ -156,9 +170,12 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         final WebAuthnAuthenticationContext authnContext = getWebAuthnAuthenticationContext(prc);
         final ByteArray challenge = authnContext.getPublicKeyCredentialRequestOptions().getChallenge();
         
+        // Blank the userHandle when creating the assertion response.
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+        // Blank userHandle in response as no allow credentials, should not happen if we have filtered all the 
+        // credentials
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), true);
+                secondRegistration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), true);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -170,9 +187,12 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         result.getSecond().setCurrentState("DisplayWebAuthnView");
         result.getSecond().resume(externalContext);
         
-        // Policy failure will return the user to the webauthn login page
-        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        // Require userHandle if no allowCredentials.
+        assertEquals(result.getSecond().getOutcome().getId(), AuthnEventIds.INVALID_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
         
     }
 
+    
+
 }
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
index f93b363..5292bce 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlowWithPolicy.java
@@ -14,6 +14,8 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.flow;
 
+import static org.testng.Assert.assertEquals;
+
 import java.util.Map;
 
 import javax.annotation.Nonnull;
@@ -32,11 +34,12 @@ import com.yubico.webauthn.data.PublicKeyCredential;
 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;
+import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.collection.Pair;
 
 
 /**
- * Flow tests for the passwordless flow.
+ * Flow tests for the passwordless flow where the 'filter credential after authentication' policy engine is active
  */
 @ContextConfiguration(
         locations = {
@@ -48,7 +51,7 @@ import net.shibboleth.shared.collection.Pair;
                 TestWebAuthnPasswordlessWithPolicyApplicationContextInitializer.class
                 }
         )
-/* The policy is set to REJECT.*/
+
 public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
     
     /** Flow ID. */
@@ -61,13 +64,25 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         super(FLOW_ID);
     }
     
-    /* Should end on username collection when starting the flow.*/
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 1 credential</li>
+     * <li>Credential request options contains an allowCredentials with 1 credential</li>
+     * <li>(Fail) the credential is filtered by the policy. </li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
-    public void testPasswordlessFlow_WithPolicy() throws Exception {
+    public void testPasswordlessFlow_WithPolicy_Filtered() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, 
+                        Base64Support.decodeURLSafe(FILTERED_CREDENTIAL_ID));
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
@@ -116,17 +131,94 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         
     }
     
-    /* 
-     * 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.
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 0 credential</li>
+     * <li>Credential request options contains an empty allowCredentials</li>
+     * <li>(fail) the policy will reject the credential as the userHandle does not match to a known credential</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
+    @SuppressWarnings("null")
+    @Test
+    public void testPasswordlessFlow_WithPolicy_NoCredentials() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, 
+                        Base64Support.decodeURLSafe(FILTERED_CREDENTIAL_ID));
+        
+        
+        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", "no-credentials-user"));
+        externalContext.setEventId("proceed");
+        
+        result.getSecond().setCurrentState("CollectUsernameView");
+        result.getSecond().resume(externalContext);
+        
+        assertFlowExecutionActive(result.getSecond());
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        assertPublicKeyCredentialRequestOptions(prc, false, 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(), 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);
+        
+        // Policy failure will return the user to the webauthn login page
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        
+    }
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 1 credential</li>
+     * <li>Credential request options contains an allowCredentials with 1 credential</li>
+     * <li>UserHandle is not returned in the response, which is allowed.</li>
+     * <li>User.id is taken from the context.</li>
+     * <li>(Fail) the credential is filtered by the policy. </li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
      */
     @SuppressWarnings("null")
     @Test
     public void testPasswordlessFlow_WithPolicy_NoUserHandleInAssertion() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, 
+                        Base64Support.decodeURLSafe(FILTERED_CREDENTIAL_ID));
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
@@ -174,5 +266,72 @@ public class TestPasswordlessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
         
     }
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>Username entered has 1 credential</li>
+     * <li>Credential request options contains an allowCredentials with 1 credential</li>
+     * <li>UserHandle is not returned in the response, which is allowed.</li>
+     * <li>(Success) the credential is not filtered and is valid</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
+    @SuppressWarnings("null")
+    @Test
+    public void testPasswordlessFlow_WithPolicy_NotFiltered() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64,null);
+        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);
+        
+        // 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);
+        
+        assertEquals(result.getSecond().getOutcome().getId(), "proceed");
+        assertAuthenticationSuccessConditions(prc, true);
+        
+    }
 
 }
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 916845d..96dd034 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
@@ -81,7 +81,7 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
     public void testRegistrationFlow_RemoveCredential() throws Exception {
         //Register a credential for use and to delete.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
@@ -121,12 +121,12 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
     public void testRegistrationFlow_RemoveCredentialFromDifferentUser() throws Exception {
         //Register a credential for use
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         // Register another uses credential and try and delete it
         final CredentialRecord registrationAnotherUser = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername("another-user", registrationAnotherUser);
         
         final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
@@ -162,7 +162,7 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
     public void testRegistrationFlow_AddCredential() throws Exception {
         //Register a credential for use for authentication.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
@@ -181,7 +181,7 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         final WebAuthnRegistrationContext authnContext = getWebAuthnRegistrationContext(prc);
         final ByteArray challenge = authnContext.getPublicKeyCredentialCreationOptions().getChallenge();
         
-        final var attestationResponse = createAttestationReponse(challenge.getBytes());
+        final var attestationResponse = createAttestationReponse(challenge.getBytes(), null);
         final String attestationResponseJson = jsonMapper.writeValueAsString(attestationResponse);
         setHttpFormRequest("POST", Map.of(ExtractPublicKeyCredentialAttestationFromFormRequest.DEFAULT_PARAMETER_NAME,
                 attestationResponseJson, 
@@ -213,14 +213,14 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         final var anotherUserDisplayName = "another user";
         final var anotherUserUserIdB64 = "2fixsNecxhORZpf4LMU9nA==";
         final CredentialRecord registration = 
-                createCredentialRegistration(anotherUserUsername, anotherUserDisplayName, anotherUserUserIdB64);
+                createCredentialRegistration(anotherUserUsername, anotherUserDisplayName, anotherUserUserIdB64, null);
         credentialRepo.addRegistrationByUsername(anotherUserUsername, registration);
         
         assertEquals(credentialRepo.getCredentialIdsForUsername(anotherUserUsername).size(),1);
         
         //Register a credential for the actual user to authenticate with
         final CredentialRecord registrationCorrectUser = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registrationCorrectUser);
         
         assertEquals(credentialRepo.getCredentialIdsForUsername(USERNAME).size(),1);
@@ -271,7 +271,7 @@ public class TestRegistrationFlow extends AbstractWebAuthnFlowTest{
         
         // Create a credential for the other users UserId/UserHandle. This should be stored against the actual user
         // We should not be able to trick the system into one user registering a credential for another user.
-        final var attestationResponse = createAttestationReponse(challenge.getBytes());
+        final var attestationResponse = createAttestationReponse(challenge.getBytes(), null);
         final String attestationResponseJson = jsonMapper.writeValueAsString(attestationResponse);
         setHttpFormRequest("POST", Map.of(ExtractPublicKeyCredentialAttestationFromFormRequest.DEFAULT_PARAMETER_NAME,
                 attestationResponseJson, 
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 be9dfc6..5351b30 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
@@ -71,7 +71,7 @@ public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
     public void testSecondFactorFlow() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
@@ -111,6 +111,56 @@ public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
         
     }
     
+    /*
+     * Tests the 2fa 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 testSecondFactorFlow_NoUserHandle() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        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("DisplayWebAuthnView", result.getSecond());
+        assertPublicKeyCredentialRequestOptions(prc, true, false);
+        
+        // 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>
+        // Blank the userHandle
+        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);
+        
+        // assert end conditions, assume existing username principal from the faked password flow
+        assertEquals(result.getSecond().getOutcome().getId(), "proceed");
+        assertAuthenticationSuccessConditions(prc, false);
+        
+    }
+    
     /* 
      * 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.
@@ -120,7 +170,7 @@ public class TestSecondFactorFlow extends AbstractWebAuthnFlowTest{
     public void testSecondFactorFlow_WrongPreviousResult() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, USERNAME);
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/TestSecondFactorFlowWithFilterPolicy.java
similarity index 61%
copy from webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestPasswordlessFlow.java
copy to webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestSecondFactorFlowWithFilterPolicy.java
index 57e6bea..f552bc0 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/TestSecondFactorFlowWithFilterPolicy.java
@@ -19,6 +19,7 @@ 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;
@@ -31,26 +32,31 @@ 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.AuthnEventIds;
+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;
+import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.collection.Pair;
 
 
 /**
- * Flow tests for the passwordless flow.
+ * Flow tests for the 2fa 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,
-                TestWebAuthnPasswordlessApplicationContextInitializer.class
+                TestWebAuthn2FaWithFilterPolicyApplicationContextInitializer.class
                 }
         )
-public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
+public class TestSecondFactorFlowWithFilterPolicy extends AbstractWebAuthnFlowTest{
     
     /** Flow ID. */
     @Nonnull public static final String FLOW_ID = "authn/WebAuthn";
@@ -58,38 +64,27 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
     /**
      * Constructor.
      */
-    protected TestPasswordlessFlow() {
+    protected TestSecondFactorFlowWithFilterPolicy() {
         super(FLOW_ID);
     }
     
-    /* Should end on username collection when starting the flow.*/
     @SuppressWarnings("null")
     @Test
-    public void testPasswordlessFlow() throws Exception {
+    public void testSecondFactorFlow_WithPolicy_Accepted() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         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, true);
+        assertPublicKeyCredentialRequestOptions(prc, true, false);
         
         // Do assertion validation half of flow
         
@@ -112,43 +107,54 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
         result.getSecond().setCurrentState("DisplayWebAuthnView");
         result.getSecond().resume(externalContext);
         
-        // assert end conditions, no existing result or principal. So this should produce a Username principal
+        // assert end conditions, assume existing username principal from the faked password flow
         assertEquals(result.getSecond().getOutcome().getId(), "proceed");
-        assertAuthenticationSuccessConditions(prc, true);
+        assertAuthenticationSuccessConditions(prc, false);
         
     }
     
-    /* 
-     * 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 {
+    public void testSecondFactorFlow_WithPolicy_Filtered() throws Exception {
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, 
+                        Base64Support.decodeURLSafe(FILTERED_CREDENTIAL_ID));
         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());
+        //Fail if all credentials have been filtered
+        assertEquals(result.getSecond().getOutcome().getId(), AuthnEventIds.NO_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
         
-        // 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);
+    }
+    
+    /*
+     * Tests the 2fa 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 testSecondFactorFlow_NoUserHandle_NotFiltered() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        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("DisplayWebAuthnView", result.getSecond());
-        assertPublicKeyCredentialRequestOptions(prc, true, true);
+        assertPublicKeyCredentialRequestOptions(prc, true, false);
         
         // Do assertion validation half of flow
         
@@ -158,6 +164,7 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
         final ByteArray challenge = authnContext.getPublicKeyCredentialRequestOptions().getChallenge();
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+        // Blank the userHandle
         assertionResponse = createAssertionReponseFrom(
                 registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), true);
 
@@ -171,10 +178,45 @@ public class TestPasswordlessFlow extends AbstractWebAuthnFlowTest{
         result.getSecond().setCurrentState("DisplayWebAuthnView");
         result.getSecond().resume(externalContext);
         
-        // assert end conditions, no existing result or principal. So this should produce a Username principal
+        // assert end conditions, assume existing username principal from the faked password flow
         assertEquals(result.getSecond().getOutcome().getId(), "proceed");
-        assertAuthenticationSuccessConditions(prc, true);
+        assertAuthenticationSuccessConditions(prc, false);
+        
+    }
+    
+    /* 
+     * 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.
+     */
+    @SuppressWarnings("null")
+    @Test
+    public void testSecondFactorFlow_WrongPreviousResult() throws Exception {
+        //Register a credential for use.
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        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());
+        
+        
+        
+    }
+    
+    private void buildMfaContext(final AuthenticationContext ac, final String previousFlow) {
         
+        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/TestUsernameslessFlow.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestUsernameslessFlow.java
index a20c4ad..a26c296 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
@@ -63,13 +63,26 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         super(FLOW_ID, "proceed");
     }
     
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>Credential matches to a known registered credential</li>
+     * <li>Success, WebAuthn client validates the credential against the one stored.</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow() throws Exception {
         
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -107,28 +120,29 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         assertAuthenticationSuccessConditions(prc, true);
 
     }
-    
+
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>UserHandle is not included in the response, but it should.'(UserHandle) Discoverable credentials store this 
+     * identifier and MUST return it as response.userHandle in  authentication ceremonies started with an empty 
+     * allowCredentials argument.'</li>
+     * <li>Fail, a userHandle is required in the response if we have not already identified the user.</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
-    public void testUsernamelessFlow_WebAuthnUsernameDifferentThanIdPUsername() throws Exception {
+    public void testUsernamelessFlow_NoUserHandleInResponse() throws Exception {
         
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
-        
-        // Now change the username to be different than the user in the WebAuthn identity
-        final var regWithDifferentUsername = CredentialRecord.builder()
-                .withUserIdentity(registration.getUserIdentity())
-                .withUsername("different-idp-username")
-                .withTransports(registration.getTransports())
-                .withRegistrationTime(registration.getRegistrationTime())
-                .withCredential(registration.getCredential())
-                .withCredentialNickname(registration.getNickname())
-                .withDiscoverable(registration.isDiscoverable())
-                .withUserVerified(registration.isUserVerified())
-                .build();
-        
-        credentialRepo.addRegistrationByUsername("different-idp-username", regWithDifferentUsername);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
 
@@ -147,8 +161,9 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         final ByteArray challenge = authnContext.getPublicKeyCredentialRequestOptions().getChallenge();
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+        // Blank userHandle in assertion
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), false);
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), true);
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -160,19 +175,32 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         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");
-        assertAuthenticationSuccessConditionsWithUsername(prc, true, "different-idp-username");
+        // This should fail, as userHandle is required in a usernameless flow (where no allowCredentials are set)
+        assertEquals(result.getSecond().getOutcome().getId(),  AuthnEventIds.INVALID_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
 
     }
-    
+
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>User Verification did not occur</li>
+     * <li>Credential matches to a known registered credential</li>
+     * <li>fail, on WebAuthn validation step 17, UV required</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow_UserNotVerified() throws Exception {
         
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -213,13 +241,26 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
 
     }
     
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>bad signature added</li>
+     * <li>Credential matches to a known registered credential</li>
+     * <li>fail, on WebAuthn validation step 20, bad signature</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow_BadSignature() throws Exception {
         
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -259,13 +300,26 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
 
     }
     
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>wrong origin added</li>
+     * <li>Credential matches to a known registered credential</li>
+     * <li>fail, on WebAuthn validation step 13, incorrect origin</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow_WrongOrigin() throws Exception {
         
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -305,12 +359,20 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
 
     }
     
-    /** 
-     * Test a validation error that the user identified by the overriden assertion userHandle is not in possession of 
-     * the credential used to sign the assertion response (as identified by the credentialID in the authenticator data 
-     * in the response).
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>UserHandle in response does not match to the credential used to sign the request</li>
+     * <li>Credential used to sign the request does not match to userHandle</li>
+     * <li>Fail, WebAuthn client fails to verify the credential because the credential (by its ID) is not registered
+     * for the userHandle in the authentication assertion. mWebAuthn validation step 6, no credential</li></li>
+     * </ol>
+     * 
      * 
-     * @throws Exception
+     * @throws Exception on error
      */
     @SuppressWarnings("null")
     @Test
@@ -318,14 +380,14 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
       
         //Register a credential for use for the actual/real user.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         //Register a second users credential with different 
         final var userHandleSecondUserB64 = "2fixsNecxhORZpf4LMU9nA==";
         final ByteArray userHandleBytesSecondUser = ByteArray.fromBase64(userHandleSecondUserB64);        
         final CredentialRecord registrationTwo = 
-                createCredentialRegistration("second-user", "second user", userHandleSecondUserB64);
+                createCredentialRegistration("second-user", "second user", userHandleSecondUserB64, null);
         credentialRepo.addRegistrationByUsername("second-user", registrationTwo);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -348,8 +410,8 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
         // second users userHandle. This should fail, as the userHandle does not link to a user with that credential
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
         assertionResponse = createAssertionReponseFrom(
-                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), "wrong.origin.com", 
-                userHandleBytesSecondUser.getBytes());
+                registration.getCredential().getCredentialId().getBytes(), challenge.getBytes(), 
+                rp.getIdentity().getId(), userHandleBytesSecondUser.getBytes());
 
         final String assertionResponseJson = jsonMapper.writeValueAsString(assertionResponse);
         
@@ -367,14 +429,25 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
 
     }
    
-    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>No authentication assertion in response</li>
+     * <li>fail, no authentication assertion.</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow_NoAssertionResponse() throws Exception {
         
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -412,13 +485,26 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
 
     }
     
+    
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>Challenge in request different than in the assertion response</li>
+     * <li>fail, on WebAuthn validation step 12, incorrect challenge</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow_DifferentChallenge() throws Exception {
         
         //Register a credential for use.
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -452,13 +538,25 @@ public class TestUsernameslessFlow extends AbstractWebAuthnFlowTest{
 
     }
     
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>UserHandle in response has no registered credentials</li>
+     * <li>fail, on WebAuthn validation step 6, no credentials</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow_NoRegisteredCredential() throws Exception {
         
         //Create a credential to use, but do not register it
         final CredentialRecord registration = 
-                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
                 
         final var prc = buildProfileRequestContext(false, false, null);
 
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 93936cd..7780cfb 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
@@ -14,6 +14,8 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.flow;
 
+import static org.testng.Assert.assertEquals;
+
 import java.util.Map;
 
 import javax.annotation.Nonnull;
@@ -29,6 +31,7 @@ import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 
+import net.shibboleth.idp.authn.AuthnEventIds;
 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;
@@ -60,14 +63,24 @@ public class TestUsernameslessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         super(FLOW_ID, "proceed");
     }
     
-    /* Most the policy this is using is in the TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer 
-     * initializer.*/
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>Credential matches to a known registered credential</li>
+     * <li>Fail, the credential is filtered by the policy</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
     @SuppressWarnings("null")
     @Test
     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);
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
@@ -105,5 +118,63 @@ public class TestUsernameslessFlowWithPolicy extends AbstractWebAuthnFlowTest{
 
     }
     
+    /**
+     * Checks:
+     * 
+     * <ol>
+     * <li>User selects credential</li>
+     * <li>No userHandle in the response.</li>
+     * <li>fail, userHandle is required if allowCredentials is empty.</li>
+     * </ol>
+     * 
+     * 
+     * @throws Exception on error
+     */
+    @SuppressWarnings("null")
+    @Test
+    public void testUsernamelessFlow_WithPolicy_NoUserHandle() throws Exception {
+        //Register a credential for use that is only suitable for 2FA
+        final CredentialRecord registration = 
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64, null);
+        credentialRepo.addRegistrationByUsername(USERNAME, registration);
+        
+        final var prc = buildProfileRequestContext(false, false, null);
+
+        final Pair<FlowExecutionResult, FlowExecutionImpl> result = launchExecution(FLOW_ID, null, externalContext, 
+                addToConversationScopeMap(Map.of("opensamlProfileRequestContext", prc)));
+
+        assertFlowExecutionActive(result.getSecond());
+        assertCurrentStateEquals("DisplayWebAuthnView", result.getSecond());
+        assertPublicKeyCredentialRequestOptions(prc, false, 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>
+        //Blank userHandle
+        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);
+        
+        
+        // Should fail, as no user.name or user.id in the context or userHandle in the response
+        assertEquals(result.getSecond().getOutcome().getId(), AuthnEventIds.INVALID_CREDENTIALS);
+        assertAuthenticationFailureConditions(prc);
+
+    }
+    
 
 }
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthn2FaWithFilterPolicyApplicationContextInitializer.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthn2FaWithFilterPolicyApplicationContextInitializer.java
new file mode 100644
index 0000000..e55dbfe
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthn2FaWithFilterPolicyApplicationContextInitializer.java
@@ -0,0 +1,51 @@
+/*
+ * 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 TestWebAuthn2FaWithFilterPolicyApplicationContextInitializer
+        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(TestWebAuthn2FaWithFilterPolicyApplicationContextInitializer.class);
+
+    /** {@inheritDoc} */
+    @Override public void initialize(@Nonnull final ConfigurableApplicationContext applicationContext) {
+        final MockPropertySource mock = new MockPropertySource("2fa-mock-properties");
+        mock.setProperty("idp.authn.webauthn.2fa.enabled", "true");
+        mock.setProperty("idp.authn.webauthn.2fa.allowedPreviousFactors", "authn/Password");
+        mock.setProperty("idp.authn.webauthn.credential.filter.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/flow/TestWebAuthnPasswordlessWithFilterPolicyApplicationContextInitializer.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnPasswordlessWithFilterPolicyApplicationContextInitializer.java
new file mode 100644
index 0000000..eae00e6
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnPasswordlessWithFilterPolicyApplicationContextInitializer.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 TestWebAuthnPasswordlessWithFilterPolicyApplicationContextInitializer
+        implements ApplicationContextInitializer<ConfigurableApplicationContext> {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(TestWebAuthnPasswordlessWithFilterPolicyApplicationContextInitializer.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.filter.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 792c5a0..ce7b1d2 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
@@ -333,7 +333,7 @@ public abstract class AbstractWebAuthnTest {
      * 
      * @throws Exception on error
      */
-    protected CredentialRecord createCredentialRegistration() throws Exception {         
+    @Nonnull protected CredentialRecord createCredentialRegistration() throws Exception {         
         final var user = UserIdentity.builder()
                 .name("jdoe")
                 .displayName("John Doe")
@@ -387,7 +387,7 @@ public abstract class AbstractWebAuthnTest {
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
        return attestation;
     }
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
index 66a4c39..89e1604 100644
--- 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
@@ -129,7 +129,7 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
 
             @Override
             public String getId() {
-                return "Dummy Accept Policy";
+                return "Mock Accept Policy";
             }
 
             @Override
@@ -155,7 +155,7 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
     
     /* 
      * Test to ensure if a different userId got into the authentication context, which does not match to the 
-     * credential ID used to sign the assertion, the policy will not run.
+     * credential ID used to sign the assertion, the policy will reject the credential.
      */
     @Test
     public void testCredentialPolicy_Accept_DifferentUserIDInContext() throws Exception {
@@ -164,7 +164,7 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
 
             @Override
             public String getId() {
-                return "Dummy Accept Policy";
+                return "Mock Accept Policy";
             }
 
             @Override
@@ -188,7 +188,8 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
         context.setUserId("thisisdifferent".getBytes(StandardCharsets.UTF_8));
         
         final Event event = action.execute(src);
-        assertNull(event);
+        assert event != null;
+        assertEquals(event.getId(),  WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
     }
     
     @Test
@@ -198,7 +199,7 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
 
             @Override
             public String getId() {
-                return "Dummy Reject Policy";
+                return "Mock Reject Policy";
             }
 
             @Override
@@ -233,7 +234,7 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
 
             @Override
             public String getId() {
-                return "Dummy Reject Policy";
+                return "Mock Reject Policy";
             }
 
             @Override
@@ -258,7 +259,10 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
         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.*/
+    /* 
+     * Policy will reject credential because userHandle is not present in the assertion and userId is not present 
+     * in the context.
+     */
     @Test
     public void testCredentialPolicy_NoUserHandle_NoUserId() throws Exception {
         
@@ -266,41 +270,7 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
 
             @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);
-        assertNull(event);
-    }
-    
-    /* 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_RejectionEnabled() throws Exception {
-        
-        action.setRejectIfNoCredentialFound(true);
-        action.setCredentialPolicy(new CredentialPolicy() {
-
-            @Override
-            public String getId() {
-                return "Dummy Reject Policy";
+                return "Mock Reject Policy";
             }
 
             @Override
@@ -325,5 +295,6 @@ public class CheckCredentialPolicyTest extends AbstractWebAuthnTest {
         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/FilterCredentialsOnPolicyTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/FilterCredentialsOnPolicyTest.java
new file mode 100644
index 0000000..ad78cac
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/FilterCredentialsOnPolicyTest.java
@@ -0,0 +1,210 @@
+/*
+ * 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.assertNull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+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.EnhancedCredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.MockAuthenticator;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * Tests for {@link FilterCredentialsOnPolicy}.
+ */
+public class FilterCredentialsOnPolicyTest extends AbstractWebAuthnTest {
+    
+    /** The action to test.*/
+    private FilterCredentialsOnPolicy action;
+    
+    /** The WebAuthn context.*/
+    private WebAuthnAuthenticationContext context;
+    
+    
+    @Override
+    @BeforeMethod
+    public void setup() throws Exception {
+        super.setup();
+        context = addWebAuthnAuthenticationContext();
+        
+        action = new FilterCredentialsOnPolicy();
+        action.setWebAuthnClient(client);
+        action.setCredentialRepository(credentialRepo);
+        
+        mockAuthenticator = new MockAuthenticator(RPID);
+        
+        context.setUsername(USERNAME);        
+    }
+    
+    @Test
+    public void testAcceptAll_SingleCredential() throws Exception {
+        context.setExistingCredentials(CollectionSupport.setOf(
+                new EnhancedCredentialRecord(createCredentialRegistration())));
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Mock Accept Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, 
+                    final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.ACCEPT;
+            }
+            
+        });
+        
+        action.initialize();
+        final Event event = action.execute(src);
+        assertNull(event);
+        
+        assertEquals(context.getExistingCredentials().size(), 1);       
+
+    }
+    
+    @Test
+    public void testAcceptAll_MultiCredential() throws Exception {
+        context.setExistingCredentials(CollectionSupport.setOf(
+                new EnhancedCredentialRecord(createCredentialRegistration()), 
+                new EnhancedCredentialRecord(createCredentialRegistration())));
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Mock Accept Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, 
+                    final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.ACCEPT;
+            }
+            
+        });
+        
+        action.initialize();
+        final Event event = action.execute(src);
+        assertNull(event);
+        
+        assertEquals(context.getExistingCredentials().size(), 2);       
+
+    }
+    
+    @Test
+    public void testRejectAll_SingleCredential() throws Exception {
+        context.setExistingCredentials(CollectionSupport.setOf(
+                new EnhancedCredentialRecord(createCredentialRegistration())));
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Mock Reject Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, 
+                    final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.REJECT;
+            }
+            
+        });
+        
+        action.initialize();
+        final Event event = action.execute(src);
+        assert event != null;
+        assertEquals(event.getId(),  AuthnEventIds.NO_CREDENTIALS);
+        
+        assertEquals(context.getExistingCredentials().size(), 0);       
+
+    }
+    
+    @Test
+    public void testRejectAll_MultiCredential() throws Exception {
+        context.setExistingCredentials(CollectionSupport.setOf(
+                new EnhancedCredentialRecord(createCredentialRegistration()),
+                new EnhancedCredentialRecord(createCredentialRegistration())));
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Mock Reject Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, 
+                    final ProfileRequestContext prc) {
+               return CredentialPolicyOutcome.REJECT;
+            }
+            
+        });
+        
+        action.initialize();
+        final Event event = action.execute(src);
+        assert event != null;
+        assertEquals(event.getId(),  AuthnEventIds.NO_CREDENTIALS);
+        
+        assertEquals(context.getExistingCredentials().size(), 0);       
+
+    }
+    
+    @Test
+    public void testRejectOne_MultiCredential() throws Exception {
+        
+        final var credentialToReject = new EnhancedCredentialRecord(createCredentialRegistration());
+        context.setExistingCredentials(CollectionSupport.setOf(
+                credentialToReject,
+                new EnhancedCredentialRecord(createCredentialRegistration())));
+        
+        action.setCredentialPolicy(new CredentialPolicy() {
+
+            @Override
+            public String getId() {
+                return "Mock Reject credential "+credentialToReject.getCredentialRecord().getCredentialIdBase64()
+                        +" Policy";
+            }
+
+            @Override
+            public CredentialPolicyOutcome evaluate(final EnhancedCredentialRecord credential, 
+                    final ProfileRequestContext prc) {
+               if (credential == credentialToReject) {
+                   return CredentialPolicyOutcome.REJECT;
+               }
+               return CredentialPolicyOutcome.ACCEPT;
+            }
+            
+        });
+        
+        action.initialize();
+        final Event event = action.execute(src);
+        assertNull(event);
+        
+        assertEquals(context.getExistingCredentials().size(), 1);       
+
+    }
+
+}
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 5ea3e78..44d83cc 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
@@ -81,7 +81,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
@@ -136,7 +136,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
@@ -169,7 +169,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         webAuthnContext.setPublicKeyCredentialAssertionResponse(assertion);
         
         final Event event = lookup.execute(src);
-        assertEquals(event.getId(), AuthnEventIds.NO_CREDENTIALS);
+        assertEquals(event.getId(), AuthnEventIds.INVALID_CREDENTIALS);
     }
     
     @SuppressWarnings("null")
@@ -184,7 +184,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         // Do not register and store the credential attestation
         
@@ -214,7 +214,7 @@ public class LookupRegisteredCredentialsFromUserHandleTest extends AbstractWebAu
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         // Do not register and store the credential attestation
         
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
index 030947a..f20b31a 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentialsTest.java
@@ -74,7 +74,7 @@ public class LookupRegisteredCredentialsTest extends AbstractWebAuthnTest {
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final var barray = ByteArray.fromBase64(USER_HANDLE_B64);
         assert barray.getBase64().equals(USER_HANDLE_B64);
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 e0d7d04..aa367cd 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
@@ -77,7 +77,7 @@ public class ValidateWebAuthnAssertionTest extends AbstractWebAuthnTest {
         
         attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         credentialRequestOptions =
                 PublicKeyCredentialRequestOptions.builder()
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/RejectOneCredentialPolicyRule.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/RejectOneCredentialPolicyRule.java
new file mode 100644
index 0000000..4139926
--- /dev/null
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/RejectOneCredentialPolicyRule.java
@@ -0,0 +1,57 @@
+/*
+ * 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 java.util.Arrays;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.DecodingException;
+
+/**
+ * A simple mock policy that REJECTS the credential specified
+ */
+public class RejectOneCredentialPolicyRule extends AbstractCredentialPolicyRule{
+    
+    /** The ID of the credential to reject.*/
+    private final byte[] rejectedCredential;
+    
+    /**
+     * Constructor. 
+     * 
+     * @param rejectedCredentialIdB64 the ID of the credential to reject
+     */
+    public RejectOneCredentialPolicyRule(final String rejectedCredentialIdB64) throws DecodingException {
+        rejectedCredential = Base64Support.decode(rejectedCredentialIdB64);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected CredentialPolicyOutcome doEvaluate(final EnhancedCredentialRecord credential, 
+            final ProfileRequestContext prc,
+            final WebAuthnAuthenticationContext webAuthnContext) {
+        
+        if (Arrays.equals(rejectedCredential, 
+                credential.getCredentialRecord().getCredential().getCredentialId().getBytes())) {
+            return CredentialPolicyOutcome.REJECT;     
+        }
+        return CredentialPolicyOutcome.ACCEPT; 
+        
+    }
+
+}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
index 4e5c587..90eec9a 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistrationSerializerTest.java
@@ -68,7 +68,7 @@ public class CredentialRegistrationSerializerTest extends AbstractWebAuthnTest {
         
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
-                        Base64Support.decode(USER_HANDLE_B64));
+                        Base64Support.decode(USER_HANDLE_B64), null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
index c11ebf0..bc0a1e5 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/IdPStorageServiceCredentialRespositoryTest.java
@@ -88,7 +88,8 @@ public class IdPStorageServiceCredentialRespositoryTest extends AbstractWebAuthn
         
         // Need to register a new credential first
         final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
-                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, userHandle);
+                mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
+                        userHandle, null);
         
         final RegisteredCredential credential = RegisteredCredential.builder()
                 .credentialId(attestation.getId())
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 01dc0b1..cd7d204 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
@@ -173,6 +173,7 @@ public class MockAuthenticator {
      * @param challenge the challenge to create the response from.
      * @param clientData the client data passed by the 'client' (browser) during authentication.
      * @param userHandle the userID that was present in the PublicKeyCredentialCreationOptions
+     * @param credentialIdOverride the ID of the credential. If {@code null} a new one will be created.
      *   
      * @returns the public key credential Attestation
      * 
@@ -181,13 +182,15 @@ public class MockAuthenticator {
     //TODO do we need challenge here, it goes in the clientData? And userHandle is only for quick reference
     public com.yubico.webauthn.data.PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>  
         createAuthenticatorAttestationResponse(@Nonnull @NotEmpty final String challenge,
-                final Map<String, String> clientData, final byte[] userHandle) throws Exception {
+                final Map<String, String> clientData, final byte[] userHandle, final byte[] credentialIdOverride) 
+                        throws Exception {
         
         final CombinedKey createdKey = generateECDSA_256_P256_Key();
         assert createdKey != null;
         
         final String aaguidHex = AUTHENTICATOR_ATTESTATION_GUID_HEX; //generateRandomIdentifierHex(32);
-        final byte[] credentialId = generateRandomIdentifierBytes(32);
+        final byte[] credentialId = credentialIdOverride != null ? credentialIdOverride : 
+            generateRandomIdentifierBytes(32);
         
         final byte[] attestationObject = createAttestationObject(createdKey, aaguidHex, credentialId);
         
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
index 6b29aa0..7965bd3 100644
--- 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
@@ -17,15 +17,16 @@
     <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"/>
+        <bean id="RejectOneCredentialPolicy" parent="shibboleth.authn.WebAuthn.AbstractCredentialPolicyRule"
+                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.RejectOneCredentialPolicyRule"
+                c:_0="ZmlsdGVyZWQtY3JlZGVudGlhbA=="/>
     </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