[java-idp-plugin-webauthn] 01/04: JWEBAUTHN-27 - Add basic authenticator policy

Phil Smart philip.smart at jisc.ac.uk
Fri Nov 8 16:24:44 UTC 2024


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=ced1ec9f10bf136da43d175a4d86ec4e77831a21

commit ced1ec9f10bf136da43d175a4d86ec4e77831a21
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Nov 7 17:32:54 2024 +0000

    JWEBAUTHN-27 - Add basic authenticator policy
    
     - Remove authenticator capabilities from credential record
     - Remove the Inspectors
     - Make the registration and authentication policies dynamic
     - Make the credential labelling (for the view) dynamic.
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-27
---
 .../policy/AuthenticatorCapabilitiesInspector.java |  47 --------
 .../authn/webauthn/policy/CredentialPolicy.java    |   4 +-
 .../authn/webauthn/storage/CredentialRecord.java   |  45 --------
 .../impl/AdminSearchCredentialsLookupFunction.java |  48 ++++++++
 .../impl/AttachAuthenticatorCapabilities.java      | 124 ---------------------
 .../impl/AuthenticatorCapabilitiesLabeller.java    |  89 ---------------
 ...torPolicy.java => CheckRegistrationPolicy.java} |  10 +-
 ...stingRegistrationCredentialsLookupStrategy.java |  49 ++++++++
 .../admin/impl/LabelAdminCredentialRecords.java    |  90 ---------------
 .../admin/impl/LabelCredentialRecords.java         |  41 +++++--
 .../admin/impl/LookupCredentialsForUser.java       |  24 +---
 .../SecondFactorOnlyCredentialLabeller.java}       |  66 +++++++----
 .../admin/impl/StorePublicKeyCredential.java       |   1 -
 ...AbstractAuthenticatorCapabilitiesInspector.java | 121 --------------------
 ...ChainingAuthenticatorCapabilitiesInspector.java |  89 ---------------
 .../impl/FunctionalAuthenticatorInspectorRule.java |  69 ------------
 .../webauthn/impl/AbstractWebAuthnAction.java      |  33 ++++++
 .../authn/webauthn/impl/CheckCredentialPolicy.java |  23 +++-
 ...ingAuthenticationCredentialsLookupStrategy.java |  53 +++++++++
 .../webauthn/impl/LookupRegisteredCredentials.java |  21 +---
 .../LookupRegisteredCredentialsFromUserHandle.java |   9 +-
 .../policy/impl/AbstractCredentialPolicyRule.java  |  10 +-
 .../policy/impl/ChainingCredentialPolicyRule.java  |  12 +-
 .../impl/FunctionalCredentialPolicyRule.java       |   8 +-
 .../impl/SecondFactorOnlyCredentialPolicyRule.java |  73 +++++++++---
 .../webauthn-management-beans.xml                  |  19 ++--
 .../webauthn-registration-beans.xml                |  46 +++-----
 .../webauthn-registration-flow.xml                 |   3 +-
 .../authn/WebAuthn/webauthn-abstract-beans.xml     |   9 +-
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |   3 +-
 ...nfig.xml => webauthn-authentication-config.xml} |   5 +-
 .../authn/webauthn/conf/authn/webauthn-config.xml  |  19 +++-
 .../conf/authn/webauthn-management-config.xml      |  25 -----
 .../conf/authn/webauthn-registration-config.xml    |  36 +-----
 .../conf/authn/webauthn-registration.properties    |  11 +-
 .../authn/webauthn/conf/authn/webauthn.properties  |   2 +
 .../idp/plugin/authn/webauthn/module.properties    |  46 ++++----
 .../admin/impl/CheckAuthenticatorPolicyTest.java   |   6 +-
 .../webauthn/flow/AbstractWebAuthnFlowTest.java    |  22 +---
 .../webauthn/flow/TestAdminManagementFlow.java     |   3 +-
 .../authn/webauthn/flow/TestPasswordlessFlow.java  |   3 +-
 .../authn/webauthn/flow/TestRegistrationFlow.java  |   3 +-
 .../authn/webauthn/flow/TestSecondFactorFlow.java  |   3 +-
 .../authn/webauthn/flow/TestUsernameslessFlow.java |   3 +-
 .../flow/TestUsernameslessFlowWithPolicy.java      |   9 +-
 ...essWithPolicyApplicationContextInitializer.java |   4 +-
 .../authn/webauthn/impl/AbstractWebAuthnTest.java  |  14 ---
 .../impl/ChainingCredentialPolicyRuleTest.java     |  24 ++--
 .../SecondFactorOnlyCredentialPolicyRuleTest.java  |  67 -----------
 .../webauthn/storage/impl/MockAuthenticator.java   |   5 +-
 .../authn/webauthn/test-beans-authentication.xml   |  14 +--
 .../authn/webauthn/test-beans-registration.xml     |  30 -----
 ...est-beans-authentication.xml => test-beans.xml} |  24 ++--
 53 files changed, 508 insertions(+), 1109 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/AuthenticatorCapabilitiesInspector.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/AuthenticatorCapabilitiesInspector.java
deleted file mode 100644
index a736bd5..0000000
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/AuthenticatorCapabilitiesInspector.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.admin.policy;
-
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import com.yubico.fido.metadata.AAGUID;
-
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.component.IdentifiedComponent;
-
-/**
- * An API for inspecting the capabilities of an authenticator and describing that in a Map. The authenticator is 
- * identified by its AAGUID.
- */
-public interface AuthenticatorCapabilitiesInspector extends IdentifiedComponent {
-    
-    /**
-     * Inspect the capabilities of this authenticator and return any capabilities/properties/labels/tags appropriate. 
-     * 
-     * @param aaguid the authenticator attestation GUID.
-     * @param prc the profile request context
-     * 
-     * @return a map of capabilities/properties/labels/tags relating to the authenticator, could be empty.
-     */
-    @Nonnull @NotLive @Unmodifiable 
-    Map<String, String> inspect(@Nullable final AAGUID aaguid, @Nonnull final ProfileRequestContext prc);
-
-}
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/CredentialPolicy.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/CredentialPolicy.java
index 3c37cf4..374ed7a 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/CredentialPolicy.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/CredentialPolicy.java
@@ -18,7 +18,7 @@ import javax.annotation.Nonnull;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.shared.component.IdentifiedComponent;
 
 /**
@@ -57,7 +57,7 @@ public interface CredentialPolicy extends IdentifiedComponent {
      * @return {@link CredentialPolicyOutcome#ACCEPT} if allowed, {@link CredentialPolicyOutcome#REJECT} if rejected, 
      * and {@link CredentialPolicyOutcome#IGNORE} otherwise.
      */
-    CredentialPolicyOutcome evaluate(@Nonnull final CredentialRecord credential, 
+    CredentialPolicyOutcome evaluate(@Nonnull final EnhancedCredentialRecord credential, 
             @Nonnull final ProfileRequestContext prc);
 
 }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRecord.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRecord.java
index a84896a..0f61bc5 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRecord.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRecord.java
@@ -16,7 +16,6 @@ package net.shibboleth.idp.plugin.authn.webauthn.storage;
 
 import java.time.Instant;
 import java.util.Collections;
-import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.SortedSet;
@@ -37,9 +36,7 @@ import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
 import com.yubico.webauthn.data.UserIdentity;
 
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
-import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
@@ -82,12 +79,6 @@ public final class CredentialRecord {
     /** Was the user verified during registration. */
     private final boolean userVerified;
     
-    /** 
-     * Capabilities of the authenticator as determined by the registration process and any configured authenticator 
-     * inspectors (not necessarily just from FIDO metadata). 
-     */
-    @Nonnull private final Map<String,String> authenticatorCapabilities;
-
     /**
      * 
      * Builder constructor.
@@ -104,8 +95,6 @@ public final class CredentialRecord {
         this.discoverable = builder.discoverable;
         this.userVerified = builder.userVerified;
         this.aaguid = builder.aaguid;
-        this.authenticatorCapabilities = builder.authenticatorCapabilities;
-
     }
 
     /**
@@ -234,16 +223,6 @@ public final class CredentialRecord {
         return credential.getCredentialId().getHex();
     }
     
-    /**
-     * Get the capabilities of the authenticator that created this credential.
-     * 
-     * @return the authenticatorCapabilities.
-     */
-    @JsonGetter("authenticatorCapabilities")
-    @Nonnull @NotLive @Unmodifiable public Map<String, String> getAuthenticatorCapabilities() {
-        return CollectionSupport.copyToMap(authenticatorCapabilities);
-    }
-    
     /**
      * Convert the credential registration into a {@link PublicKeyCredentialDescriptor}.
      * 
@@ -401,15 +380,6 @@ public final class CredentialRecord {
          * @return the next builder stage
          */
         @Nonnull public IBuildStage withAaguid(byte[] aaguid);
-        
-        /**
-         * Set the capabilities of the authenticator as determined by the registration process 
-         * (not FIDO necessarily FIDO metadata). 
-         * 
-         * @param capabilities the capabilities of the authenticator
-         * @return the next builder stage
-         */
-        @Nonnull public IBuildStage withAuthenticatorCapabilities(@Nullable final Map<String,String> capabilities);
 
         /**
          * Build this credential registration.
@@ -441,11 +411,6 @@ public final class CredentialRecord {
         private boolean userVerified;        
         /** The AAGUID of the authenticator.*/
         @Nullable private byte[] aaguid;
-        /** 
-         * Capabilities of the authenticator as determined by the registration process 
-         * (not FIDO neccisarily FIDO metadata). 
-         */
-        @Nonnull private Map<String,String> authenticatorCapabilities;
 
         /** Constructor.*/
         @SuppressWarnings("null")
@@ -454,7 +419,6 @@ public final class CredentialRecord {
             discoverable = Optional.empty();            
             userVerified = false;
             transports = Collections.emptySortedSet();
-            authenticatorCapabilities = Collections.emptyMap();
         }
 
         @Override
@@ -529,15 +493,6 @@ public final class CredentialRecord {
             aaguid = authenticatorGuid;
             return this;
         }
-        
-        @Override
-        @JsonProperty("authenticatorCapabilities")
-        public IBuildStage withAuthenticatorCapabilities(@Nullable final Map<String, String> capabilities) {
-            if (capabilities != null) {
-                authenticatorCapabilities = CollectionSupport.copyToMap(capabilities);
-            }
-            return this;
-        }
     }
 
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AdminSearchCredentialsLookupFunction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AdminSearchCredentialsLookupFunction.java
new file mode 100644
index 0000000..023074f
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AdminSearchCredentialsLookupFunction.java
@@ -0,0 +1,48 @@
+/*
+ * 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.admin.impl;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * A credential lookup function that returns the credentials belonging to the user that has been searched for in the
+ * management interface.
+ */
+public class AdminSearchCredentialsLookupFunction 
+    implements Function<ProfileRequestContext, Collection<EnhancedCredentialRecord>>{
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<EnhancedCredentialRecord> apply(@Nullable final ProfileRequestContext input) {
+        if (input == null) {
+            return CollectionSupport.emptyList();
+        }
+        final WebAuthnManagementContext manCtx = input.getSubcontext(WebAuthnManagementContext.class);
+        if (manCtx == null) {
+            return CollectionSupport.emptyList();
+        }
+        return manCtx.getFoundCredentials();
+    }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AttachAuthenticatorCapabilities.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AttachAuthenticatorCapabilities.java
deleted file mode 100644
index 89da113..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AttachAuthenticatorCapabilities.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.admin.impl;
-
-import java.util.Map;
-import java.util.Optional;
-
-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 com.yubico.fido.metadata.AAGUID;
-import com.yubico.webauthn.data.AttestedCredentialData;
-import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
-import com.yubico.webauthn.data.ByteArray;
-import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
-import com.yubico.webauthn.data.PublicKeyCredential;
-
-import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
-import net.shibboleth.idp.plugin.authn.webauthn.admin.policy.AuthenticatorCapabilitiesInspector;
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * Attach capabilities to the authenticator that has created the credential registration. For example, this 
- * authenticator should only be used for 2FA. 
- */
-public class AttachAuthenticatorCapabilities extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
-    
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(AttachAuthenticatorCapabilities.class);
-    
-    /** The stashed attestation response.*/
-    @NonnullBeforeExec 
-    private PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation;
-    
-    /** The inspector that checks the capabilities of the authenticator.*/
-    @Nullable private AuthenticatorCapabilitiesInspector authenticatorInspector;
-    
-    /**
-     * Constructor.
-     */
-    protected AttachAuthenticatorCapabilities() {
-        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
-    }
-    
-    /**
-     * Set the inspector that checks the capabilities of the authenticator.
-     * 
-     * @param inspector the authenticator inspector to set.
-     */
-    public void setAuthenticatorInspector(@Nullable final AuthenticatorCapabilitiesInspector inspector) {
-        checkSetterPreconditions();
-        authenticatorInspector = inspector;
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnRegistrationContext context) {
-        
-        if (!super.doPreExecute(profileRequestContext, context)) {
-            return false;
-        }
-       
-        attestation = context.getPublicKeyCredentialAttestationResponse();
-        if (attestation == null) {
-            log.error("{} Attestaion not available in registration context", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION_CTX);
-            return false;
-        }
-        
-        return true;
-    }
-    
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnRegistrationContext context) {
-        
-        log.trace("{} Attaching authenticator capabilities", getLogPrefix());
-        
-        final AuthenticatorCapabilitiesInspector localInspector = authenticatorInspector;
-        if (localInspector == null) {
-            // If no inspector, there is nothing to do
-            log.trace("{} No authenticator inspector to apply", getLogPrefix());
-            return;
-        }
-        
-        final Optional<AttestedCredentialData> attestedCredData = 
-                attestation.getResponse().getParsedAuthenticatorData().getAttestedCredentialData();
-        
-        if (attestedCredData.isEmpty()) {
-            log.warn("{} Attested Credential Data can not assess authenticator capabilities", getLogPrefix());
-            return;
-            
-        }
-        
-        final ByteArray aaguid = attestedCredData.get().getAaguid();        
-        final AAGUID authenticatorAttestationGUID = new AAGUID(aaguid);
-        final Map<String, String> capabilities = 
-                localInspector.inspect(authenticatorAttestationGUID, profileRequestContext);
-        context.setAuthenticatorCapabilities(capabilities);
-        
-        
-    }
-
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AuthenticatorCapabilitiesLabeller.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AuthenticatorCapabilitiesLabeller.java
deleted file mode 100644
index f1667e3..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AuthenticatorCapabilitiesLabeller.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.admin.impl;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.function.BiFunction;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-import javax.annotation.concurrent.ThreadSafe;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
-import net.shibboleth.shared.annotation.ParameterName;
-import net.shibboleth.shared.annotation.constraint.NonnullElements;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.annotation.constraint.Unmodifiable;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.collection.Pair;
-
-/**
- * An authenticator capabilities labeller function that maps authenticator capabilities to labels for display.
- */
- at ThreadSafe
-public class AuthenticatorCapabilitiesLabeller 
-    implements BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> {
-    
-    /** Map authenticator capabilities to labels to display in the registration pages.*/
-    @Nonnull @NonnullElements @Unmodifiable @NotLive private Map<Pair<String,String>,List<String>> capabilityToLabelMap;
-    
-    /**
-     * 
-     * Constructor.
-     *
-     * @param map the authenticator to capabilities map. The key is a {@link Pair} where the first entry is the
-     * capability and the second entry is the value of that capability to match on. The Value of the map is the label
-     * to set.
-     */
-    public AuthenticatorCapabilitiesLabeller(
-            @Nullable @ParameterName(name="capabilityToLabelMap") final Map<Pair<String,String>,List<String>> map) {
-        if (map != null) {
-            capabilityToLabelMap = CollectionSupport.copyToMap(map);
-        } else {
-            capabilityToLabelMap = CollectionSupport.emptyMap();
-        }
-        
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public List<String> apply(@Nullable final EnhancedCredentialRecord cred, 
-            @Nullable final ProfileRequestContext prc) {
-        
-        if (cred == null) {
-            return CollectionSupport.emptyList();
-        }
-        final List<String> labels = new ArrayList<>();
-        final CredentialRecord credential = cred.getCredentialRecord();
-
-        final Map<String, String> capabilities = credential.getAuthenticatorCapabilities();
-        for (final Entry<Pair<String,String>, List<String>> entry : capabilityToLabelMap.entrySet()) {
-            if (capabilities.containsKey(entry.getKey().getFirst())) {
-                final String value = capabilities.get(entry.getKey().getFirst());
-                if (value != null && value.equals(entry.getKey().getSecond())) {
-                    labels.addAll(entry.getValue());
-                }
-                
-            }
-        }
-        return labels;
-    }
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckAuthenticatorPolicy.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckRegistrationPolicy.java
similarity index 94%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckAuthenticatorPolicy.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckRegistrationPolicy.java
index b893eec..d45b721 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckAuthenticatorPolicy.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckRegistrationPolicy.java
@@ -41,17 +41,17 @@ import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * A policy engine action that checks with the configured policy if the authenticator can be used to register 
- * credentials with the IdP. 
+ * A registration policy engine action that checks with the configured policy if the authenticator can be used to 
+ * register credentials with the IdP. 
  * 
  * @event {WebAuthnRegistrationEventIds#INVALID_REGISTRATION_CTX}
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post the authenticator is allowed to register a credential, or an error event is triggered
  */
-public class CheckAuthenticatorPolicy extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
+public class CheckRegistrationPolicy extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(CheckAuthenticatorPolicy.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CheckRegistrationPolicy.class);
 
     /** The stashed attestation response.*/
     @NonnullBeforeExec 
@@ -63,7 +63,7 @@ public class CheckAuthenticatorPolicy extends AbstractWebAuthnAction<WebAuthnReg
     /**
      * Constructor.
      */
-    protected CheckAuthenticatorPolicy() {
+    protected CheckRegistrationPolicy() {
         super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
     }
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExistingRegistrationCredentialsLookupStrategy.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExistingRegistrationCredentialsLookupStrategy.java
new file mode 100644
index 0000000..789183f
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExistingRegistrationCredentialsLookupStrategy.java
@@ -0,0 +1,49 @@
+/*
+ * 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.admin.impl;
+
+import java.util.Collection;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * A credential lookup function that returns the list of existing credentials from the 
+ * {@link WebAuthnRegistrationContext}.
+ */
+public class ExistingRegistrationCredentialsLookupStrategy 
+        implements Function<ProfileRequestContext, Collection<EnhancedCredentialRecord>>{
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<EnhancedCredentialRecord> apply(@Nullable final ProfileRequestContext input) {
+        if (input == null) {
+            return CollectionSupport.emptyList();
+        }
+        final WebAuthnRegistrationContext webAuthnCtx = 
+                input.getSubcontext(WebAuthnRegistrationContext.class);
+        if (webAuthnCtx != null) {
+            return webAuthnCtx.getExistingCredentials();
+        }
+        return CollectionSupport.emptyList();
+    }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelAdminCredentialRecords.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelAdminCredentialRecords.java
deleted file mode 100644
index ca2c315..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelAdminCredentialRecords.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * 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.admin.impl;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.function.BiFunction;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
-import net.shibboleth.shared.annotation.constraint.NotLive;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-
-/**
- * Use the labelling function to add labels to each {@link EnhancedCredentialRecord} found in the context. The labels
- * can then be displayed in the registration view.
- * 
- * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
- * @post Add labels to each enhanced credential record in the context
- */
-// TODO another admin function which is almost identical to the registration variant
-public class LabelAdminCredentialRecords extends AbstractWebAuthnAction<WebAuthnManagementContext> {
-    
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(LabelAdminCredentialRecords.class);
-    
-    /** The labeller used to generate labels for the credential, for display only.*/
-    @Nonnull @NotLive private BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labeller;
-    
-    /**
-     * Constructor.
-     *
-     * @param defaultStrategy
-     */
-    protected LabelAdminCredentialRecords() {
-        super(new ChildContextLookup<>(WebAuthnManagementContext.class).
-                compose(new ChildContextLookup<>(AuthenticationContext.class)));
-        labeller = (cred, prc) -> CollectionSupport.emptyList();
-    }
-    
-    /**
-     * Set the labeller used to generate labels for the credential, for display only.
-     * 
-     * @param labeller The labeller to set.
-     */
-    public void setLabeller(@Nonnull 
-            final BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labellerIn) {
-        checkSetterPreconditions();
-        labeller = Constraint.isNotNull(labellerIn, "labeller can not be null");
-    }
-    
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnManagementContext context) {
-        
-        final Collection<EnhancedCredentialRecord> credentials = context.getFoundCredentials();
-        
-        log.trace("{} Labelling '{}' credentials",getLogPrefix(), credentials.size());
-        for (final EnhancedCredentialRecord credential : credentials) {
-            final List<String> labels = labeller.apply(credential, profileRequestContext);
-            if (labels != null) {
-                log.trace("{} Added labels '{}' for credential '{}'", 
-                        getLogPrefix(), labels, credential.getCredentialRecord().getCredentialIdBase64Url());
-                credential.setLabels(labels);
-            }
-        }    
-    }
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelCredentialRecords.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelCredentialRecords.java
index 14f71a5..5aa8c9b 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelCredentialRecords.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LabelCredentialRecords.java
@@ -17,19 +17,20 @@ package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.BiFunction;
+import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
+import net.shibboleth.idp.authn.AbstractAuthenticationAction;
 import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -41,7 +42,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
  * @post Add labels to each enhanced credential record in the context
  */
-public class LabelCredentialRecords extends AbstractWebAuthnAction<BaseWebAuthnContext> {
+public class LabelCredentialRecords extends AbstractAuthenticationAction {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(LabelCredentialRecords.class);
@@ -49,17 +50,39 @@ public class LabelCredentialRecords extends AbstractWebAuthnAction<BaseWebAuthnC
     /** The labeller used to generate labels for the credential, for display only.*/
     @Nonnull @NotLive private BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> labeller;
     
+    @NonnullAfterInit 
+    private Function<ProfileRequestContext, Collection<EnhancedCredentialRecord>> credentialsLookupStrategy;
+    
     /**
      * Constructor.
      *
      * @param defaultStrategy
      */
     protected LabelCredentialRecords() {
-        super(new ChildContextLookup<>(BaseWebAuthnContext.class).
-                compose(new ChildContextLookup<>(AuthenticationContext.class)));
         labeller = (cred, prc) -> CollectionSupport.emptyList();
     }
     
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        if (credentialsLookupStrategy == null) {
+            throw new ComponentInitializationException("credentialsLookupStrategy can not be null");
+        }
+    }
+    
+    /**
+     * Set the lookup strategy to find credentials to attach capabilities to.
+     * 
+     * @param strategy The credentials lookup strategy to set.
+     */
+    public void setCredentialsLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, Collection<EnhancedCredentialRecord>> strategy) {
+        checkSetterPreconditions();
+        credentialsLookupStrategy = Constraint.isNotNull(strategy,
+                "credentialsLookupStrategy can not be null");
+    }
+    
     /**
      * Set the labeller used to generate labels for the credential, for display only.
      * 
@@ -72,11 +95,11 @@ public class LabelCredentialRecords extends AbstractWebAuthnAction<BaseWebAuthnC
     }
     
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final BaseWebAuthnContext context) {
+            @Nonnull final AuthenticationContext context) {
         
-        final Collection<EnhancedCredentialRecord> credentials = context.getExistingCredentials();
+        final Collection<EnhancedCredentialRecord> credentials = credentialsLookupStrategy.apply(profileRequestContext);
         
-        log.trace("{} Labelling '{}' credentials",getLogPrefix(), credentials.size());
+        log.trace("{} Labelling '{}' credentials, with labeller '{}'",getLogPrefix(), credentials.size(), labeller);
         for (final EnhancedCredentialRecord credential : credentials) {
             final List<String> labels = labeller.apply(credential, profileRequestContext);
             if (labels != null) {
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
index 58a3b03..e4bbfd0 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupCredentialsForUser.java
@@ -15,8 +15,6 @@
 package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
 
 import javax.annotation.Nonnull;
 
@@ -24,14 +22,10 @@ import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.yubico.fido.metadata.AAGUID;
-import com.yubico.webauthn.data.ByteArray;
-
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.collection.CollectionSupport;
@@ -84,23 +78,9 @@ public class LookupCredentialsForUser extends AbstractWebAuthnAction<WebAuthnMan
             assert userToSearchFor != null;
             final Collection<CredentialRecord> credentials = 
                     repository.getRegistrationsByUsername(userToSearchFor);      
-            log.debug("{} Found '{}' credentials", getLogPrefix(), credentials.size());
-            
-            final Collection<EnhancedCredentialRecord> enhancedCredentialRegistrations = 
-                    new HashSet<>(credentials.size());
-            
-            credentials.stream().filter(Objects::nonNull).forEach(cred -> {
-                assert cred != null;
-                final EnhancedCredentialRecord enhancedRecord = new EnhancedCredentialRecord(cred);                 
-                final byte[] aaguid = cred.getAaguid();
-                if (aaguid != null && aaguid.length == 16) {
-                    enhancedRecord.setAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(aaguid)));
-                    enhancedRecord.setAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(aaguid))));
-                }
-                enhancedCredentialRegistrations.add(enhancedRecord);
-            });
+            log.debug("{} Found '{}' credentials", getLogPrefix(), credentials.size());           
             
-            context.setFoundCredentials(enhancedCredentialRegistrations);
+            context.setFoundCredentials(enhancedCredentialRecord(credentials));
         }
 
     }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/SecondFactorOnlyAuthenticatorInspector.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/SecondFactorOnlyCredentialLabeller.java
similarity index 58%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/SecondFactorOnlyAuthenticatorInspector.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/SecondFactorOnlyCredentialLabeller.java
index 7d09a4b..9a98f89 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/SecondFactorOnlyAuthenticatorInspector.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/SecondFactorOnlyCredentialLabeller.java
@@ -12,46 +12,61 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl;
+package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.yubico.fido.metadata.AAGUID;
+import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.plugin.authn.webauthn.authn.AuthenticatorSupport;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
- * Tag credentials created by authenticators that should only be used for second-factor authentication.
+ * A credential labeller function that labels credentials as 'SecondFactor' if the authenticator that created the
+ * credential should only be used as a second factor, and not sole factor of authentication.
  */
-public class SecondFactorOnlyAuthenticatorInspector extends AbstractAuthenticatorCapabilitiesInspector {
-    
-    /** The name of the capability.*/
-    @Nonnull @NotEmpty public static final String CAPABILITY_NAME = "2faOnly";
+ at ThreadSafe
+public class SecondFactorOnlyCredentialLabeller 
+    implements BiFunction<EnhancedCredentialRecord, ProfileRequestContext, List<String>> {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(SecondFactorOnlyAuthenticatorInspector.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(SecondFactorOnlyCredentialLabeller.class);
+    
+    /** The default label to use for second factor only credentials.*/
+    @Nonnull @NotEmpty private static final String DEFAULT_LABEL = "SecondFactorOnly";
     
     /** A set of authenticators that should only be used for second factor authentication, based on their AAGUID.*/
     @Nonnull @NotLive @Unmodifiable private Set<AAGUID> secondFactorOnlyAuthenticators;
     
-    /** Constructor.*/
-    public SecondFactorOnlyAuthenticatorInspector() {
+    /** The label to use for second factor only credentials.*/
+    @Nonnull @NotEmpty private String label;
+    
+    /**
+     * 
+     * Constructor.
+    **/
+    public SecondFactorOnlyCredentialLabeller() {
         secondFactorOnlyAuthenticators = CollectionSupport.emptySet();
+        label = DEFAULT_LABEL;
     }
     
     /**
@@ -60,7 +75,6 @@ public class SecondFactorOnlyAuthenticatorInspector extends AbstractAuthenticato
      * @param allowed The second factor authenticators to set.
      */
     public void setSecondFactorOnlyAuthenticators(final Set<String> allowed) {
-        checkSetterPreconditions();
         if (allowed != null) {
             secondFactorOnlyAuthenticators = allowed.stream().filter(strAAGUID -> 
             StringSupport.trimOrNull(strAAGUID)!=null)
@@ -75,15 +89,29 @@ public class SecondFactorOnlyAuthenticatorInspector extends AbstractAuthenticato
             }).filter(Objects::nonNull).collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
         }
     }
+    
+    /**
+     * Set the label to use for second factor only credentials.
+     * 
+     * @param label The label to set.
+     */
+    public void setLabel(@Nonnull final String labelIn) {       
+        label = Constraint.isNotNull(labelIn, "label can not be null");
+    }
 
     /** {@inheritDoc} */
     @Override
-    public Map<String, String> doInspect(final AAGUID aaguid, final ProfileRequestContext prc) {
-        final boolean secondFactorOnly = secondFactorOnlyAuthenticators.contains(aaguid);
-        final HashMap<String,String> capabilities = new HashMap<>();
-        capabilities.put(CAPABILITY_NAME, secondFactorOnly ? "true" : "false");
-        return CollectionSupport.copyToMap(capabilities);
+    public List<String> apply(@Nullable final EnhancedCredentialRecord cred, 
+            @Nullable final ProfileRequestContext prc) {
+        
+        if (cred == null) {
+            return CollectionSupport.emptyList();
+        }
+        final AAGUID aaguidToCheck = new AAGUID(new ByteArray(cred.getCredentialRecord().getAaguid()));
+        final boolean secondFactorOnly = secondFactorOnlyAuthenticators.contains(aaguidToCheck);
+        if (secondFactorOnly) {
+            return CollectionSupport.listOf(label);
+        }  
+        return CollectionSupport.emptyList();
     }
-
-
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
index 88ef6ed..79bf7ae 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/StorePublicKeyCredential.java
@@ -142,7 +142,6 @@ public class StorePublicKeyCredential extends AbstractWebAuthnAuditingAction<Web
                     .withCredentialNickname(context.getCredentialNickname())
                     .withDiscoverable(isDiscoverable)
                     .withUserVerified(registrationResult.isUserVerified())
-                    .withAuthenticatorCapabilities(context.getAuthenticatorCapabilities())
                     .build();
             
             final boolean added = repository.addRegistrationByUsername(username, registration);  
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/AbstractAuthenticatorCapabilitiesInspector.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/AbstractAuthenticatorCapabilitiesInspector.java
deleted file mode 100644
index 830df91..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/AbstractAuthenticatorCapabilitiesInspector.java
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * 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.admin.policy.impl;
-
-import java.util.Map;
-import java.util.function.BiPredicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.yubico.fido.metadata.AAGUID;
-import com.yubico.fido.metadata.FidoMetadataService;
-
-import net.shibboleth.idp.plugin.authn.webauthn.admin.policy.AuthenticatorCapabilitiesInspector;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * A base class for {@link AuthenticatorCapabilitiesInspector authenticator inspectors}. Can be enabled and disabled 
- * by the activiation condition. 
- * 
- * <p>Returns a map of authenticator capabilities or properties.</p>
- */
-public abstract class AbstractAuthenticatorCapabilitiesInspector extends AbstractIdentifiableInitializableComponent 
-            implements AuthenticatorCapabilitiesInspector {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractAuthenticatorCapabilitiesInspector.class);
-    
-    /** FIDO metadata service resolver.*/ 
-    @Nullable private FidoMetadataService fidoMetadataService;  
-    
-    /** Does this inspector apply? Default is true. */
-    @Nonnull private BiPredicate<AAGUID, ProfileRequestContext> activationCondition;
-    
-    /** Constructor.*/
-    protected AbstractAuthenticatorCapabilitiesInspector() {
-        //default is always true
-        activationCondition = (prc,claims) -> true;
-    }
-    
-    /**
-     * Set an activation condition for this inspector.
-     * 
-     * @param condition condition to set
-     */
-    public void setActivationConditionStrategy(@Nonnull final BiPredicate<AAGUID, ProfileRequestContext> condition) {
-        checkSetterPreconditions();        
-        activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
-    }
-    
-    /**
-     * Set an activation condition for this inspector.
-     * 
-     * @param flag the flag to set
-     */
-    public void setActivationCondition(final boolean flag) {
-        checkSetterPreconditions();        
-        activationCondition = flag ? (prc,claims) -> true : (prc,claims) -> false;
-    }
-    
-    /**
-     * Set the FIDO Metadata service.
-     * 
-     * @param trustSource the FIDO Metadata service.
-     */
-    public void setFidoMetadataService(@Nullable final FidoMetadataService trustSource) {
-        checkSetterPreconditions();
-        fidoMetadataService = trustSource;
-    }
-    
-    /**
-     * Get the metadata service to use. 
-     * 
-     * @return the metadata service.
-     */
-    @Nullable protected FidoMetadataService getFidoMetadataService() {
-        return fidoMetadataService;
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    public Map<String, String> inspect(@Nullable final AAGUID aaguid, @Nonnull final ProfileRequestContext prc) {
-        if (!activationCondition.test(aaguid, prc)) {
-            //not active for this request
-            log.trace("AuthenticatorCapabilitiesInspector '{}' not active for this request", getId());
-            return CollectionSupport.emptyMap();
-        }
-        return doInspect(aaguid, prc);
-    }
-
-    /**
-     * Execute the inspector. Implementations should override this method.
-     * 
-     * @param aaguid the authenticator attestation GUID.
-     * @param prc the profile request context
-     * 
-     * @return a map of capabilities.
-     */
-    @Nonnull 
-    protected abstract Map<String, String> doInspect(@Nullable AAGUID aaguid, @Nonnull ProfileRequestContext prc);
-    
-    
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/ChainingAuthenticatorCapabilitiesInspector.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/ChainingAuthenticatorCapabilitiesInspector.java
deleted file mode 100644
index 9e638e2..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/ChainingAuthenticatorCapabilitiesInspector.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.admin.policy.impl;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.yubico.fido.metadata.AAGUID;
-
-import net.shibboleth.idp.plugin.authn.webauthn.admin.policy.AuthenticatorCapabilitiesInspector;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * A {@link AbstractAuthenticatorCapabilitiesInspector} implementation that is based on a chain of 
- * configured inspectors. The outcomes from each are aggregated together (the last entry for a given key will overwrite
- * the previous entry).
- */
-public class ChainingAuthenticatorCapabilitiesInspector extends AbstractAuthenticatorCapabilitiesInspector {
-    
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(ChainingAuthenticatorCapabilitiesInspector.class);
-    
-    /** An ordered chain of authenticator inspectors.*/
-    private List<AuthenticatorCapabilitiesInspector> authenticatorInspectorChain;
-    
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (authenticatorInspectorChain == null) {
-            throw new ComponentInitializationException("List of authenticator inspectors can not be null");
-        } 
-    }
-    
-    /**
-     * Set the chain of inspectors that should be used to evaluate the authenticator.
-     * 
-     * @param chain The authenticator inspector chain to set.
-     */
-    public void setAuthenticatorInspectorChain(@Nullable final List<AuthenticatorCapabilitiesInspector> chain) {
-        checkSetterPreconditions();
-        if (chain != null) {
-            authenticatorInspectorChain = chain;
-        } else {
-            authenticatorInspectorChain = CollectionSupport.emptyList();
-        }
-    }    
-
-    /** {@inheritDoc} */
-    @Override
-    protected Map<String,String> doInspect(@Nullable final AAGUID aaguid, @Nonnull final ProfileRequestContext prc) {
-        
-        final Map<String, String> allOutcomes = new HashMap<>();
-        for (final AuthenticatorCapabilitiesInspector inspector : authenticatorInspectorChain) {
-            if (log.isTraceEnabled()) {
-                log.trace("Trying AuthenticatorCapabilitiesInspector '{}' for authenticator '{}'", inspector.getId(),
-                        aaguid != null ? aaguid.asGuidString() : "unknown");
-            }
-            final Map<String,String> outcome = inspector.inspect(aaguid, prc);
-            log.trace("AuthenticatorCapabilitiesInspector '{}' produced result '{}'", inspector.getId(), outcome);
-            allOutcomes.putAll(outcome);
-        }
-        log.trace("All AuthenticatorCapabilitiesInspector produced result '{}'", allOutcomes);
-        return CollectionSupport.copyToMap(allOutcomes);
-    }
-
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/FunctionalAuthenticatorInspectorRule.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/FunctionalAuthenticatorInspectorRule.java
deleted file mode 100644
index 6609919..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/policy/impl/FunctionalAuthenticatorInspectorRule.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * 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.admin.policy.impl;
-import java.util.Map;
-import java.util.function.BiFunction;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import com.yubico.fido.metadata.AAGUID;
-
-import net.shibboleth.idp.plugin.authn.webauthn.admin.policy.AuthenticatorCapabilitiesInspector;
-import net.shibboleth.idp.plugin.authn.webauthn.policy.impl.FunctionalRuleContext;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * A {@link AuthenticatorCapabilitiesInspector} that uses a {@link BiFunction} to inspect the authenticator that 
- * created the credential registration. 
- */
-public class FunctionalAuthenticatorInspectorRule extends AbstractAuthenticatorCapabilitiesInspector {
-
-    /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(FunctionalAuthenticatorInspectorRule.class);
-    
-    /** The function to run. By default this returns an empty map.*/
-    @Nonnull private BiFunction<AAGUID, FunctionalRuleContext, Map<String, String>> inspector;
-    
-    /** Constructor.*/
-    public FunctionalAuthenticatorInspectorRule() {
-        inspector = (cred, prc) -> CollectionSupport.emptyMap();
-    }
-    
-    /**
-     * Set the inspector function rule to run. 
-     * 
-     * @param function the inspector to set.
-     */
-    public void setInspector(@Nonnull 
-            final BiFunction<AAGUID, FunctionalRuleContext, Map<String, String>> function) {
-        checkSetterPreconditions();
-        inspector = Constraint.isNotNull(function, "Inspector function can not be null");
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    @Nonnull protected Map<String, String> doInspect(final AAGUID aaguid, final ProfileRequestContext prc) {
-        log.debug("Running AuthenticatorInspector rule function '{}'", this.getId());
-        final Map<String, String> capabilities = inspector.apply(aaguid, 
-                new FunctionalRuleContext(prc, getFidoMetadataService()));
-        return capabilities == null ? CollectionSupport.emptyMap() : capabilities;
-    }
-
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
index a976bfc..8e4570e 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
@@ -14,6 +14,9 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 import java.util.function.Function;
 
@@ -34,6 +37,8 @@ import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
 import net.shibboleth.idp.plugin.authn.webauthn.metadata.AaguidEntry;
 import net.shibboleth.idp.plugin.authn.webauthn.metadata.impl.PasskeyAaguidMetadataService;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -41,6 +46,7 @@ import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
@@ -299,6 +305,33 @@ public class AbstractWebAuthnAction<T> extends AbstractProfileAction {
         }
         return null;
     }
+    
+    /**
+     * Create {@link EnhancedCredentialRecord EnhancedCredentialRecords} from the given 
+     * {@link CredentialRecord CredentialRecords} and add any appropriate metadata to each record. 
+     * 
+     * @param credentials the credentials to convert to {@link EnhancedCredentialRecord EnhancedCredentialRecords}.
+     * 
+     * @return a collection of enhanced credential records
+     */
+    @NotLive @Unmodifiable @Nonnull protected Collection<EnhancedCredentialRecord> enhancedCredentialRecord(
+            final Collection<CredentialRecord> credentials) {
+        
+        final Collection<EnhancedCredentialRecord> enhancedCredentialRegistrations = 
+                new HashSet<>(credentials.size());
+        
+        credentials.stream().filter(Objects::nonNull).forEach(cred -> {
+            assert cred != null;
+            final EnhancedCredentialRecord enhancedRecord = new EnhancedCredentialRecord(cred);                 
+            final byte[] aaguid = cred.getAaguid();
+            if (aaguid != null && aaguid.length == 16) {
+                enhancedRecord.setAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(aaguid)));
+                enhancedRecord.setAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(aaguid))));
+            }
+            enhancedCredentialRegistrations.add(enhancedRecord);
+        });
+        return CollectionSupport.copyToList(enhancedCredentialRegistrations);
+    }
 
     
 
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 bd4cc23..fc89925 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
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
+import java.util.Collection;
 import java.util.Optional;
 
 import javax.annotation.Nonnull;
@@ -37,6 +38,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationCo
 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.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
@@ -139,28 +141,37 @@ public class CheckCredentialPolicy extends AbstractWebAuthnAction<WebAuthnAuthen
             return;
         }
         
-        final Optional<CredentialRecord> credential = 
-                repository.getRegistrationByUserHandleAndCredentialId(assertion.getId(), userHandle.get());
+        final Collection<EnhancedCredentialRecord> registeredCredentials = context.getExistingCredentials();
+        
+        final Optional<EnhancedCredentialRecord> credential = registeredCredentials.stream().filter(cred -> {
+            final CredentialRecord credRecord = cred.getCredentialRecord();
+            return assertion.getId().equals(credRecord.getCredential().getCredentialId()) &&
+                    userHandle.get().equals(credRecord.getUserIdentity().getId());
+        }).findFirst();
+
         if (credential.isEmpty()) {
             log.trace("{} UserHandle '{}' has no registered credential",getLogPrefix(), userHandle.get().getHex());
             return;
         }
         
-        final CredentialPolicyOutcome outcome = localPolicy.evaluate(credential.get(), profileRequestContext);
+        final EnhancedCredentialRecord credentialToEvaluate = credential.get();
+        assert credentialToEvaluate != null;
+        final CredentialPolicyOutcome outcome = localPolicy.evaluate(credentialToEvaluate, profileRequestContext);
+        
         if (outcome == CredentialPolicyOutcome.REJECT) {
             log.warn("{} CredentialPolicy '{}' has rejected credential '{}'", getLogPrefix(), localPolicy.getId(),
-                    credential.get().getCredential().getCredentialId().getBase64Url());
+                    credential.get().getCredentialRecord().getCredentialIdBase64Url());
             authnContext.ensureSubcontext(AuthenticationErrorContext.class).getClassifiedErrors().add(
                     WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
             ActionSupport.buildEvent(profileRequestContext, WebAuthnAuthenticationEventIds.CREDENTIAL_POLICY_REJECTION);
             return;
         } else if (outcome == CredentialPolicyOutcome.IGNORE) {
             log.trace("{} CredentialPolicy '{}' was not active for credential '{}', accepting", getLogPrefix(), 
-                    localPolicy.getId(), credential.get().getCredential().getCredentialId().getBase64Url());
+                    localPolicy.getId(), credential.get().getCredentialRecord().getCredentialIdBase64Url());
             return;
         }
         log.debug("{} CredentialPolicy '{}' accepted credential '{}'", getLogPrefix(), localPolicy.getId(),
-                    credential.get().getCredential().getCredentialId().getHex());
+                credential.get().getCredentialRecord().getCredentialIdBase64Url());
              
     } 
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExistingAuthenticationCredentialsLookupStrategy.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExistingAuthenticationCredentialsLookupStrategy.java
new file mode 100644
index 0000000..1f3efcf
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExistingAuthenticationCredentialsLookupStrategy.java
@@ -0,0 +1,53 @@
+/*
+ * 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.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * A credential lookup function that returns the list of existing credentials from the 
+ * {@link WebAuthnAuthenticationContext}.
+ */
+public class ExistingAuthenticationCredentialsLookupStrategy 
+        implements Function<ProfileRequestContext, Collection<EnhancedCredentialRecord>>{
+
+    /** {@inheritDoc} */
+    @Override
+    public Collection<EnhancedCredentialRecord> apply(@Nullable final ProfileRequestContext input) {
+        if (input == null) {
+            return CollectionSupport.emptyList();
+        }
+        final AuthenticationContext authnCtx = input.getSubcontext(AuthenticationContext.class);
+        if (authnCtx != null) {
+            final WebAuthnAuthenticationContext webAuthnCtx = 
+                    authnCtx.getSubcontext(WebAuthnAuthenticationContext.class);
+            if (webAuthnCtx != null) {
+                return webAuthnCtx.getExistingCredentials();
+            }
+        }
+        return CollectionSupport.emptyList();
+    }
+
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index 4b98a4f..3be8627 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
@@ -15,8 +15,6 @@
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
 import java.util.Collection;
-import java.util.HashSet;
-import java.util.Objects;
 import java.util.Optional;
 import java.util.function.Predicate;
 
@@ -28,7 +26,6 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.yubico.fido.metadata.AAGUID;
 import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -36,7 +33,6 @@ import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.authn.WebAuthnAuthenticationEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.WebAuthnCredentialRepository;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -168,26 +164,13 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebA
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         } 
-        
+        assert username != null;
         final Collection<CredentialRecord> credentials =
                 repository.getRegistrationsByUsername(username);   
 
         log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(), credentials.size(), username);
         
-        final Collection<EnhancedCredentialRecord> enhancedCredentialRegistrations = 
-                new HashSet<>(credentials.size());
-        
-        credentials.stream().filter(Objects::nonNull).forEach(cred -> {
-            assert cred != null;
-            final EnhancedCredentialRecord enhancedRecord = new EnhancedCredentialRecord(cred);            
-            final byte[] aaguid = cred.getAaguid();
-            if (cred.getAaguid() != null) {
-                enhancedRecord.setAuthenticatorMetadata(getAuthenticatorMetadata(new ByteArray(aaguid)));
-                enhancedRecord.setAaguidMetadata(getAaguidMetadata(new AAGUID(new ByteArray(aaguid))));
-            }
-            enhancedCredentialRegistrations.add(enhancedRecord);
-        });
-        context.setExistingCredentials(enhancedCredentialRegistrations);
+        context.setExistingCredentials(enhancedCredentialRecord(credentials));
         
         final Optional<ByteArray> userHandle = repository.getUserHandleForUsername(username);
         if (userHandle.isPresent()) {
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 d17e8d4..a11a656 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
@@ -46,10 +46,8 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * An action that lookups existing registered credentials from the userHandle supplied in the authenticators assertion
- * response. If no credentials exist and the trigger condition is set, an error event will be produced.
- * 
- * <p>Does not actually set the found credentials back onto the context. This is a sanity check that credentials exist, 
- * and allows flow branching if none are found.</p>
+ * 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>
@@ -162,7 +160,8 @@ public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnA
                 if (credentials.isEmpty()) {
                     log.debug("{} Could not find any registered credentials for userHandle '{}'", getLogPrefix(), 
                             userHandle.get().getBase64());
-                } else {
+                } else {         
+                    context.setExistingCredentials(enhancedCredentialRecord(credentials));
                     credentialsFound = true;
                     log.debug("{} Found registered credentials for userHandle '{}' (determined username as '{}')", 
                             getLogPrefix(), userHandle.get().getBase64(), potentialUsername.get());
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/AbstractCredentialPolicyRule.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/AbstractCredentialPolicyRule.java
index 5b06b99..9587377 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/AbstractCredentialPolicyRule.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/AbstractCredentialPolicyRule.java
@@ -29,7 +29,7 @@ import com.yubico.fido.metadata.FidoMetadataService;
 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.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
 import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
 import net.shibboleth.shared.logic.Constraint;
@@ -54,7 +54,7 @@ public abstract class AbstractCredentialPolicyRule extends AbstractIdentifiableI
     @Nullable private FidoMetadataService fidoMetadataService;  
     
     /** Does this policy rule apply? Default is true. */
-    @Nonnull private BiPredicate<CredentialRecord, ProfileRequestContext> activationCondition;
+    @Nonnull private BiPredicate<EnhancedCredentialRecord, ProfileRequestContext> activationCondition;
     
     /** Lookup strategy to locate the WebAuthn context. */
     @Nonnull private Function<ProfileRequestContext,WebAuthnAuthenticationContext> webauthnContextLookupStrategy;
@@ -87,7 +87,7 @@ public abstract class AbstractCredentialPolicyRule extends AbstractIdentifiableI
      * @param condition condition to set
      */
     public void setActivationConditionStrategy(
-            @Nonnull final BiPredicate<CredentialRecord, ProfileRequestContext> condition) {
+            @Nonnull final BiPredicate<EnhancedCredentialRecord, ProfileRequestContext> condition) {
         checkSetterPreconditions();        
         activationCondition = Constraint.isNotNull(condition, "Activation condition cannot be null");
     }
@@ -130,7 +130,7 @@ public abstract class AbstractCredentialPolicyRule extends AbstractIdentifiableI
      */
     @Override
     public CredentialPolicyOutcome evaluate(
-            @Nonnull final CredentialRecord credential, @Nonnull final ProfileRequestContext prc) {
+            @Nonnull final EnhancedCredentialRecord credential, @Nonnull final ProfileRequestContext prc) {
         if (!activationCondition.test(credential, prc)) {
             //not active for this request
             log.trace("CredentialPolicy rule '{}' not active for this request", getId());
@@ -153,7 +153,7 @@ public abstract class AbstractCredentialPolicyRule extends AbstractIdentifiableI
      * @return the credential policy outcome
      */
     protected abstract CredentialPolicyOutcome doEvaluate(
-            @Nonnull final CredentialRecord credential, @Nonnull final ProfileRequestContext prc,
+            @Nonnull final EnhancedCredentialRecord credential, @Nonnull final ProfileRequestContext prc,
             @Nonnull final WebAuthnAuthenticationContext webAuthnContext);
 
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRule.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRule.java
index 1a8b8dd..9de34c8 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRule.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRule.java
@@ -25,6 +25,7 @@ import org.slf4j.Logger;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -69,30 +70,31 @@ public class ChainingCredentialPolicyRule extends AbstractCredentialPolicyRule {
     /** {@inheritDoc} */
     @Override
     protected CredentialPolicyOutcome doEvaluate(
-            @Nonnull final CredentialRecord credential, @Nonnull final ProfileRequestContext prc,
+            @Nonnull final EnhancedCredentialRecord credential, @Nonnull final ProfileRequestContext prc,
             @Nonnull final WebAuthnAuthenticationContext webAuthnContext) {
         
+        final CredentialRecord credentialRecord = credential.getCredentialRecord();
         for (final CredentialPolicy policy : credentialPolicyChain) {
             if (log.isTraceEnabled()) {
                 log.trace("Trying CredentialPolicy rule '{}' for credential '{}'", policy.getId(),
-                        credential.getCredentialIdBase64Url());
+                        credentialRecord.getCredentialIdBase64Url());
             }
             final CredentialPolicyOutcome outcome = policy.evaluate(credential, prc);
             if (outcome  == CredentialPolicyOutcome.REJECT) {
                 if (log.isDebugEnabled()) {
                     log.debug("CredentialPolicy rule '{}' rejected credential '{}'", policy.getId(), 
-                            credential.getCredentialIdBase64Url());
+                            credentialRecord.getCredentialIdBase64Url());
                 }
                 return CredentialPolicyOutcome.REJECT;
             } else if (outcome == CredentialPolicyOutcome.IGNORE){
                 if (log.isDebugEnabled()) {
                     log.debug("CredentialPolicy rule '{}' was ignored for credential '{}'", policy.getId(), 
-                            credential.getCredentialIdBase64Url());
+                            credentialRecord.getCredentialIdBase64Url());
                 }
             } else {
                 if (log.isTraceEnabled()) {
                     log.trace("CredentialPolicy rule '{}' accepted credential '{}'", policy.getId(),  
-                            credential.getCredentialIdBase64Url());
+                            credentialRecord.getCredentialIdBase64Url());
                 }
             }
         }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/FunctionalCredentialPolicyRule.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/FunctionalCredentialPolicyRule.java
index 936bed5..e397ce7 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/FunctionalCredentialPolicyRule.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/FunctionalCredentialPolicyRule.java
@@ -23,7 +23,7 @@ import org.slf4j.Logger;
 
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -37,7 +37,7 @@ public class FunctionalCredentialPolicyRule extends AbstractCredentialPolicyRule
     @Nonnull private final Logger log = LoggerFactory.getLogger(FunctionalCredentialPolicyRule.class);
     
     /** The function to run. By default this returns {@link CredentialPolicyOutcome#IGNORE}.*/
-    @Nonnull private BiFunction<CredentialRecord, FunctionalRuleContext, CredentialPolicyOutcome> rule;
+    @Nonnull private BiFunction<EnhancedCredentialRecord, FunctionalRuleContext, CredentialPolicyOutcome> rule;
     
     /** Constructor.*/
     public FunctionalCredentialPolicyRule() {
@@ -50,14 +50,14 @@ public class FunctionalCredentialPolicyRule extends AbstractCredentialPolicyRule
      * @param function the rule to set.
      */
     public void setRule(@Nonnull 
-            final BiFunction<CredentialRecord, FunctionalRuleContext, CredentialPolicyOutcome> function) {
+            final BiFunction<EnhancedCredentialRecord, FunctionalRuleContext, CredentialPolicyOutcome> function) {
         checkSetterPreconditions();
         rule = Constraint.isNotNull(function, "Rule function can not be null");
     }
 
     /** {@inheritDoc} */
     @Override
-    protected CredentialPolicyOutcome doEvaluate(final CredentialRecord credential, 
+    protected CredentialPolicyOutcome doEvaluate(final EnhancedCredentialRecord credential, 
             final ProfileRequestContext prc, final WebAuthnAuthenticationContext webAuthnContext) {
         log.debug("Running CredentialPolicy rule function '{}'", this.getId());
         return rule.apply(credential, new FunctionalRuleContext(prc, getFidoMetadataService()));
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/SecondFactorOnlyCredentialPolicyRule.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/SecondFactorOnlyCredentialPolicyRule.java
index 266a016..9e3b822 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/SecondFactorOnlyCredentialPolicyRule.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/SecondFactorOnlyCredentialPolicyRule.java
@@ -13,7 +13,9 @@
  */
 package net.shibboleth.idp.plugin.authn.webauthn.policy.impl;
 
-import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -21,13 +23,20 @@ import javax.annotation.Nullable;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.SecondFactorOnlyAuthenticatorInspector;
+import com.yubico.fido.metadata.AAGUID;
+import com.yubico.webauthn.data.ByteArray;
+
+import net.shibboleth.idp.plugin.authn.webauthn.authn.AuthenticatorSupport;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
 
 /**
  * A {@link CredentialPolicy} that rejects credentials used in sole-factor mode if created by authenticators which 
@@ -38,34 +47,62 @@ public class SecondFactorOnlyCredentialPolicyRule extends AbstractCredentialPoli
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(SecondFactorOnlyCredentialPolicyRule.class);
     
+    /** A set of authenticators that should only be used for second factor authentication, based on their AAGUID.*/
+    @Nonnull @NotLive @Unmodifiable private Set<AAGUID> secondFactorOnlyAuthenticators;
+    
+    /** Constructor.*/
+    public SecondFactorOnlyCredentialPolicyRule() {
+        secondFactorOnlyAuthenticators = CollectionSupport.emptySet();
+    }
+    
+    /**
+     * Set the authenticators that should only be used for second factor authentication based on their AAGUID. 
+     * 
+     * @param allowed The second factor authenticators to set.
+     */
+    public void setSecondFactorOnlyAuthenticators(final Set<String> allowed) {
+        checkSetterPreconditions();
+        if (allowed != null) {
+            secondFactorOnlyAuthenticators = allowed.stream().filter(strAAGUID -> 
+            StringSupport.trimOrNull(strAAGUID)!=null)
+                    .map(strAAGUID -> {
+                final var aaguidBytes = AuthenticatorSupport.parse(strAAGUID);
+                if (aaguidBytes != null) {
+                    return new AAGUID(aaguidBytes);
+                } else {
+                    log.trace("AAGUID '{}' is not valid", strAAGUID);
+                    return null;
+                }                  
+            }).filter(Objects::nonNull).collect(CollectionSupport.nonnullCollector(Collectors.toSet())).get();
+        }
+    }
+    
     /** {@inheritDoc} */
     @Override
     public CredentialPolicyOutcome doEvaluate(
-            @Nonnull final CredentialRecord credential, @Nonnull final ProfileRequestContext prc,
+            @Nonnull final EnhancedCredentialRecord credential, @Nonnull final ProfileRequestContext prc,
             @Nonnull final WebAuthnAuthenticationContext webAuthnContext) {
         
-        final Map<String,String> capabilities = credential.getAuthenticatorCapabilities();
-        if (capabilities.containsKey(SecondFactorOnlyAuthenticatorInspector.CAPABILITY_NAME)) {
-            final String value = capabilities.get(SecondFactorOnlyAuthenticatorInspector.CAPABILITY_NAME);
-            final boolean isTrue = Boolean.parseBoolean(value);
-            
-            if (!webAuthnContext.isSecondFactor() && isTrue) {
-                if (log.isTraceEnabled()) {
-                    log.trace("Rejected credential '{}', authentication is sole-factor and authenticator '{}' that "
-                            + "created the credential should only be used as a second factor", 
-                            credential.getCredentialIdBase64Url(), toBase64OrUnknown(credential.getAaguid()));
-                }
-                return CredentialPolicyOutcome.REJECT;
+        final AAGUID aaguidToCheck = new AAGUID(new ByteArray(credential.getCredentialRecord().getAaguid()));
+        final boolean secondFactorOnly = secondFactorOnlyAuthenticators.contains(aaguidToCheck);
+        
+        if (!webAuthnContext.isSecondFactor() && secondFactorOnly) {
+            if (log.isTraceEnabled()) {
+                log.trace("Rejected credential '{}', authentication is sole-factor and authenticator '{}' that "
+                        + "created the credential should only be used as a second factor", 
+                        credential.getCredentialRecord().getCredentialIdBase64Url(), 
+                        toBase64OrUnknown(credential.getCredentialRecord().getAaguid()));
             }
+            return CredentialPolicyOutcome.REJECT;
         }
         return CredentialPolicyOutcome.ACCEPT;
     }
     
     /**
-     * Convert the value to Base64 URL encoding, or return "unknown" if there is an error.
+     * Convert the value to Base64 URL encoding or return "unknown" if there is an error.
      * 
      * @param value the value to Base64 URL encode
-     * @return the Bas64 URL encoded string.
+     * @return the Bas64 URL encoded string or "unknown".
      */
     private String toBase64OrUnknown(@Nullable final byte[] value) {
         if (value == null) {
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
index ed54578..d1ac708 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-management/webauthn-management-beans.xml
@@ -40,7 +40,6 @@
         p:webAuthnClient="#{getObject('shibboleth.authn.WebAuthn.WebAuthnAuthenticationClientFactory') ?: getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnAuthenticationClientFactory')}"
         p:credentialRepository="#{getObject('shibboleth.authn.WebAuthn.CredentialRepository') ?: getObject('shibboleth.authn.WebAuthn.DefaultCredentialRepository')}"
         p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
-  
 
     <!-- Flow beans -->
 
@@ -71,15 +70,14 @@
     <bean id="LookupCredentialsForUser" parent="AbstractWebAuthnBaseAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LookupCredentialsForUser"
         p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext" />
-        
-    <bean id="LabelCredentialRecords" parent="AbstractWebAuthnBaseAction" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LabelAdminCredentialRecords"
-        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext" 
-        p:labeller="#{getObject('%{idp.authn.webauthn.registration.labeller:shibboleth.authn.WebAuthn.ChainingCredentialLabeller}')}"/>
-        
-    <bean id="shibboleth.authn.WebAuthn.ChainingCredentialLabeller" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ChainingCredentialLabeller"
-        c:labellers="#{getObject('shibboleth.authn.WebAuthn.CredentialLabellerList')}"/>
+    
+    <bean id="LabelCredentialRecords" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LabelCredentialRecords"
+        p:labeller="#{getObject('%{idp.authn.webauthn.labeller:shibboleth.authn.WebAuthn.ChainingCredentialLabeller}')}">
+        <property name="credentialsLookupStrategy">
+            <bean id="SearchedUserCredentialLookupStrategy" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AdminSearchCredentialsLookupFunction"/>
+        </property>
+    </bean>
         
     <bean id="ExtractKeyRemovalInformationFromFormRequest" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyInformationFromFormRequest"
@@ -142,5 +140,6 @@
     </bean>
 
     <import resource="conditional:%{idp.home}/conf/authn/webauthn-management-config.xml" />
+    <import resource="conditional:%{idp.home}/conf/authn/webauthn-config.xml" />
 
 </beans>
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
index ce929a9..5891926 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-beans.xml
@@ -36,19 +36,12 @@
   
     <bean id="shibboleth.authn.WebAuthn.registration.AbstractAuthenticatorPolicyRule" scope="prototype" abstract="true"
         p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
-    
-    <bean id="shibboleth.authn.WebAuthn.registration.AbstractAuthenticatorCapabilitiesInspector" scope="prototype" abstract="true"
-        p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
      
     <bean id="shibboleth.authn.WebAuthn.registration.FunctionalAuthenticatorPolicyRule" abstract="true"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.FunctionalAuthenticatorPolicyRule" 
         p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
         
-    <bean id="shibboleth.authn.WebAuthn.registration.FunctionalAuthenticatorInspectorRule" abstract="true"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.FunctionalAuthenticatorInspectorRule" 
-        p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnFidoMetadataServiceFactory')}"/>
-        
-    
+
     <!-- Flow beans -->
     
     <bean id="PopulateInlineEnrolmentContext" scope="prototype" 
@@ -132,15 +125,14 @@
     <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnBaseAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
         p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
-        
-    <bean id="LabelCredentialRecords" parent="AbstractWebAuthnBaseAction" scope="prototype"
+
+    <bean id="LabelCredentialRecords" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LabelCredentialRecords"
-        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" 
-        p:labeller="#{getObject('%{idp.authn.webauthn.registration.labeller:shibboleth.authn.WebAuthn.ChainingCredentialLabeller}')}"/>
-        
-    <bean id="shibboleth.authn.WebAuthn.ChainingCredentialLabeller" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ChainingCredentialLabeller"
-        c:labellers="#{getObject('shibboleth.authn.WebAuthn.CredentialLabellerList')}"/>
+        p:labeller="#{getObject('%{idp.authn.webauthn.registration.labeller:shibboleth.authn.WebAuthn.ChainingCredentialLabeller}')}">
+         <property name="credentialsLookupStrategy">
+            <bean id="ExistingCredentialsLookup" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExistingRegistrationCredentialsLookupStrategy"/>
+        </property>
+    </bean>
     
     <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
@@ -194,24 +186,15 @@
         p:writeAuditLogAction="#{%{idp.authn.webauthn.registration.audit.enabled:false} ? getObject('WriteAdminAuditLog') : null}"
         p:auditContextCreationStrategy-ref="AdminAuditContextLookup" />        
 
-    <bean id="CheckAuthenticatorPolicy" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.CheckAuthenticatorPolicy"
-        p:authenticatorPolicy="#{getObject('%{idp.authn.webauthn.registration.authenticator.policy:shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorPolicy}')}"
+    <bean id="CheckRegistrationPolicy" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.CheckRegistrationPolicy"
+        p:authenticatorPolicy="#{getObject('%{idp.authn.webauthn.registration.authenticator.policy:shibboleth.authn.WebAuthn.registration.ChainedRegistrationPolicy}')}"
         p:activationCondition="%{idp.authn.webauthn.registration.authenticator.policy.enabled:false}"/>
         
-     <bean id="shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorPolicy" scope="prototype"
+     <bean id="shibboleth.authn.WebAuthn.registration.ChainedRegistrationPolicy" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.ChainingAuthenticatorPolicyRule"
-        p:authenticatorPolicyChain="#{getObject('%{idp.authn.webauthn.registration.authenticator.policy.chainedlist:shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorPolicyList}')}"/>
-    
-    <bean id="AttachAuthenticatorCapabilities" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AttachAuthenticatorCapabilities"
-        p:authenticatorInspector="#{getObject('%{idp.authn.webauthn.registration.authenticator.inspector:shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorCapabilitiesInspector}')}"
-        p:activationCondition="%{idp.authn.webauthn.registration.authenticator.inspector.enabled:false}"/>       
-         
-    <bean id="shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorCapabilitiesInspector" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.ChainingAuthenticatorCapabilitiesInspector"
-        p:authenticatorInspectorChain="#{getObject('%{idp.authn.webauthn.registration.authenticator.inspector.chainedlist:shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorInspectorList}')}"/>  
-        
+        p:authenticatorPolicyChain="#{getObject('%{idp.authn.webauthn.registration.authenticator.policy.chainedlist:shibboleth.authn.WebAuthn.registration.ChainedRegistrationPolicyList}')}"/>         
+              
     <bean id="ValidateAuthenticatorAttestationResponse" parent="AbstractWebAuthnRegistrationAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ValidateAuthenticatorAttestationResponse" />
 
@@ -270,5 +253,6 @@
     </bean>
     
     <import resource="conditional:%{idp.home}/conf/authn/webauthn-registration-config.xml" />
+    <import resource="conditional:%{idp.home}/conf/authn/webauthn-config.xml" />
 
 </beans>
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
index cc6c46a..f50e19c 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
@@ -135,8 +135,7 @@
     
    <action-state id="AddKey">
         <evaluate expression="ExtractPublicKeyCredentialAttestationFromFormRequest"/>
-        <evaluate expression="CheckAuthenticatorPolicy"/>
-        <evaluate expression="AttachAuthenticatorCapabilities"/>
+        <evaluate expression="CheckRegistrationPolicy"/>        
         <evaluate expression="ValidateAuthenticatorAttestationResponse"/>
         <evaluate expression="StorePublicKeyCredential"/> 
         <evaluate expression="'proceed'" />
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml
index 98b70ba..1f483e1 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-abstract-beans.xml
@@ -34,7 +34,14 @@
         p:fidoMetadataService="#{'false'.equals('%{idp.authn.webauthn.metadata.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultWebAuthnFidoMetadataServiceFactory')}"
         p:aaguidService="#{'false'.equals('%{idp.authn.webauthn.metadata.aaguid.enabled:false}') ? null : getObject('shibboleth.authn.WebAuthn.DefaultPasskeyAaguidMetadataServiceFactory')}"/>
     
-     <!-- Used in views to calculate CSP hashes and nonces, remove and adjust beans in flow when compatibility bumped past 5.0 -->
+    
+    <!-- Beans shared across two or more flows -->
+      
+    <bean id="shibboleth.authn.WebAuthn.ChainingCredentialLabeller" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ChainingCredentialLabeller"
+        c:labellers="#{getObject('shibboleth.authn.WebAuthn.CredentialLabellerList')}"/>
+    
+    <!-- Used in views to calculate CSP hashes and nonces, remove and adjust beans in flow when compatibility bumped past 5.0 -->
     
     <bean id="WebAuthnCSPDigester" class="net.shibboleth.shared.codec.StringDigester"
         c:algorithm="SHA256" c:format="BASE64" />
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 668cba4..d9dd67e 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
@@ -142,7 +142,7 @@
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentialsFromUserHandle"
         p:credentialRepository="#{getObject('shibboleth.authn.WebAuthn.CredentialRepositoryy') ?: getObject('shibboleth.authn.WebAuthn.DefaultCredentialRepository')}"
         p:triggerEventOnNoCredentials="%{idp.authn.webauthn.signalEventOnNoCredentialsRegisteredForUserHandle:false}"
-        p:noCredentialsEventId="%{idp.authn.webauthn.userHandleNoRegisteredCredentialsEventId:NoCredentialsRegisteredForUserHandle}"/>        
+        p:noCredentialsEventId="%{idp.authn.webauthn.userHandleNoRegisteredCredentialsEventId:NoCredentialsRegisteredForUserHandle}"/>    
 
     <bean id="CheckCredentialPolicy" parent="AbstractWebAuthnAuthenticationAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.CheckCredentialPolicy"
@@ -216,6 +216,7 @@
         </property>
     </bean>
     
+   <import resource="conditional:%{idp.home}/conf/authn/webauthn-authentication-config.xml" />
    <import resource="conditional:%{idp.home}/conf/authn/webauthn-config.xml" />
 
 </beans>
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-authentication-config.xml
similarity index 87%
copy from webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
copy to webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-authentication-config.xml
index 48e1dc0..ad23afd 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-authentication-config.xml
@@ -16,8 +16,9 @@
     -->
     <util:list id="shibboleth.authn.WebAuthn.ChainedCredentialPolicyList">
         <bean id="SecondFactorOnlyCredentialPolicyRule" parent="shibboleth.authn.WebAuthn.AbstractCredentialPolicyRule"
-                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.SecondFactorOnlyCredentialPolicyRule"/>
+                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.SecondFactorOnlyCredentialPolicyRule"
+                p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.authenticator.policy.secondFactorOnlyAuthenticators:null}"/>
     </util:list>
-
+    
 
 </beans>
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
index 48e1dc0..4e6ca64 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-config.xml
@@ -8,15 +8,22 @@
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
 
     default-init-method="initialize" default-destroy-method="destroy" default-lazy-init="true">
+    
+    <!-- Configuration that is shared between two or more WebAuthn flows -->
 
+        
     <!-- 
-    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.
-    
+    The default credential labeller list. Use during registration or management flows to label credential for display
+    purposes only. 
     -->
-    <util:list id="shibboleth.authn.WebAuthn.ChainedCredentialPolicyList">
-        <bean id="SecondFactorOnlyCredentialPolicyRule" parent="shibboleth.authn.WebAuthn.AbstractCredentialPolicyRule"
-                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.SecondFactorOnlyCredentialPolicyRule"/>
+    <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
+    
+        <bean id="CapabilitiesCredentialLabeller" scope="prototype"
+            class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.SecondFactorOnlyCredentialLabeller"
+            p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.authenticator.policy.secondFactorOnlyAuthenticators:null}"
+            p:label="SecondFactorOnly"/>
+        
+        <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
     </util:list>
 
 
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml
index 07605bd..92416d5 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-management-config.xml
@@ -8,31 +8,6 @@
                            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
 
     default-init-method="initialize" default-destroy-method="destroy" default-lazy-init="true">
-    
-    <!-- 
-    The default credential labeller list
-    -->
-    <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
-    
-        <bean id="CapabilitiesCredentialLabeller" scope="prototype"
-            class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AuthenticatorCapabilitiesLabeller"
-            c:_0-ref="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap"/>
-        
-        <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
-    </util:list>
-    
-    <!-- 
-    The default capabilities to labels map. The capabilities are first determined by the Inspectors.
-    -->
-    <util:map id="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap">
-        <entry>
-            <key>
-                <bean parent="shibboleth.Pair"
-                    p:first="2faOnly" p:second="true"/>
-           </key>
-           <value>SecondFactorOnly</value> 
-        </entry>
-    </util:map>
 
 
 </beans>
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml
index ce177f3..3b1a7c1 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration-config.xml
@@ -13,45 +13,11 @@
     The default list to add authenticator policies used during registration. The property 
     idp.authn.webauthn.registration.authenticator.policy.enabled must be set to true for these to take effect.
     -->
-    <util:list id="shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorPolicyList">  
+    <util:list id="shibboleth.authn.WebAuthn.registration.ChainedRegistrationPolicyList">  
         <bean id="AllowlistAuthenticatorPolicy" parent="shibboleth.authn.WebAuthn.registration.AbstractAuthenticatorPolicyRule" 
             class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.AllowlistAuthenticatorPolicy"
             p:allowedAuthenticators="%{idp.authn.webauthn.registration.authenticator.policy.allowedAuthenticators:null}"/>
     </util:list>    
         
-    <!-- 
-    The default list to add authenticator inspectors used during registration. The property 
-    idp.authn.webauthn.registration.authenticator.inspector.enabled must be set to true for these to take effect.
-    -->
-    <util:list id="shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorInspectorList">  
-        <bean id="SecondFactorOnlyInspector" parent="shibboleth.authn.WebAuthn.registration.AbstractAuthenticatorCapabilitiesInspector"
-            class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.SecondFactorOnlyAuthenticatorInspector"
-            p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.registration.authenticator.inspector.secondFactorOnlyAuthenticators:null}"/>
-    </util:list>
-    
-    <!-- 
-    The default credential labeller list
-    -->
-    <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
-    
-        <bean id="CapabilitiesCredentialLabeller" scope="prototype"
-            class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AuthenticatorCapabilitiesLabeller"
-            c:_0-ref="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap"/>
-        
-        <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
-    </util:list>
-    
-    <!-- 
-    The default capabilities to labels map. The capabilities are first determined by the Inspectors.
-    -->
-    <util:map id="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap">
-        <entry>
-            <key>
-                <bean parent="shibboleth.Pair"
-                    p:first="2faOnly" p:second="true"/>
-           </key>
-           <value>SecondFactorOnly</value> 
-        </entry>
-    </util:map>
 
 </beans>
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties
index 2cd2022..c8527e6 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-registration.properties
@@ -48,7 +48,7 @@
 # State the preference of the IdP during registration to receive an authenticator attestation. One-of 'none', 'indirect', 'direct', or 'enterprise'.
 #idp.authn.webauthn.registration.attestationConveyancePreference = none
 
-# Enable the authenticator policy engine?
+# Enable the registration policy engine?
 #idp.authn.webauthn.registration.authenticator.policy.enabled = false
 # Set the authenticator policy to use, defaults to a chained set of policies
 #idp.authn.webauthn.registration.authenticator.policy = shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorPolicy
@@ -57,15 +57,6 @@
 # When using the default chained policy; list allowed authenticator's by their attestation GUIDs (AAGUID)
 #idp.authn.webauthn.registration.authenticator.policy.allowedAuthenticators = 
 
-# Enable the authenticator inspector engine?
-#idp.authn.webauthn.registration.authenticator.inspector.enabled = false
-# Set the authenticator inspectors to use, defaults to a chained set of inspectors
-#idp.authn.webauthn.registration.authenticator.inspector = shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorCapabilitiesInspector
-# When using the default chained inspector; name the inspector list bean
-#idp.authn.webauthn.registration.authenticator.inspector.chainedlist = shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorInspectors
-# When using the default chained set of inspectors; list authenticators (by attestation GUIDs (AAGUID)) to tag as only allowed for second factor authentication
-#idp.authn.webauthn.registration.authenticator.inspector.secondFactorOnlyAuthenticators =
-
 # Allow inline self-enrolment 
 #idp.authnwebauthn.registration.allowInline = true
 
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 b9ed6db..5e70f5e 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
@@ -70,6 +70,8 @@ idp.authn.webauthn.supportedPrincipals = \
 #idp.authn.webauthn.credential.policy = shibboleth.authn.WebAuthn.ChainedCredentialPolicy
 # When using the default chained policy, which policy list should we use?
 #idp.authn.webauthn.credential.policy.chainedlist = shibboleth.authn.WebAuthn.ChainedCredentialPolicyList
+# When using the default chained set of inspectors; list authenticators (by attestation GUIDs (AAGUID)) to tag as only allowed for second factor authentication
+#idp.authn.webauthn.authenticator.policy.secondFactorOnlyAuthenticators = 
 
 # Audit
 #idp.authn.webauthn.audit.enabled = false
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties
index 860430e..ebbf6da 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/module.properties
@@ -30,35 +30,39 @@ idp.authn.WebAuthn.5.src = /net/shibboleth/idp/plugin/authn/webauthn/conf/authn/
 idp.authn.WebAuthn.5.dest = conf/authn/webauthn-management-config.xml
 idp.authn.WebAuthn.5.replace = false
 
-idp.authn.WebAuthn.6.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
-idp.authn.WebAuthn.6.dest = views/webauthn/webauthn-authn.vm
+idp.authn.WebAuthn.6.src = /net/shibboleth/idp/plugin/authn/webauthn/conf/authn/webauthn-authentication-config.xml
+idp.authn.WebAuthn.6.dest = conf/authn/webauthn-authentication-config.xml
+idp.authn.WebAuthn.6.replace = false
 
-idp.authn.WebAuthn.7.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
-idp.authn.WebAuthn.7.dest = views/webauthn/webauthn-register.vm
+idp.authn.WebAuthn.7.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
+idp.authn.WebAuthn.7.dest = views/webauthn/webauthn-authn.vm
 
-idp.authn.WebAuthn.8.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register-username.vm
-idp.authn.WebAuthn.8.dest = views/webauthn/webauthn-register-username.vm
+idp.authn.WebAuthn.8.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
+idp.authn.WebAuthn.8.dest = views/webauthn/webauthn-register.vm
 
-idp.authn.WebAuthn.9.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn-username.vm
-idp.authn.WebAuthn.9.dest = views/webauthn/webauthn-authn-username.vm
+idp.authn.WebAuthn.9.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register-username.vm
+idp.authn.WebAuthn.9.dest = views/webauthn/webauthn-register-username.vm
 
-idp.authn.WebAuthn.10.src = /net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
-idp.authn.WebAuthn.10.dest = edit-webapp/css/webauthn.css
-idp.authn.WebAuthn.10.postenable = Customize edit-webapp/css/webauthn.css and rebuild war to deploy.
+idp.authn.WebAuthn.10.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn-username.vm
+idp.authn.WebAuthn.10.dest = views/webauthn/webauthn-authn-username.vm
 
-idp.authn.WebAuthn.11.src = /net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-json.browser-ponyfill.min.js
-idp.authn.WebAuthn.11.dest = edit-webapp/js/webauthn/webauthn-json.browser-ponyfill.min.js
+idp.authn.WebAuthn.11.src = /net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
+idp.authn.WebAuthn.11.dest = edit-webapp/css/webauthn.css
+idp.authn.WebAuthn.11.postenable = Customize edit-webapp/css/webauthn.css and rebuild war to deploy.
 
-idp.authn.WebAuthn.12.src = /net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js
-idp.authn.WebAuthn.12.dest = edit-webapp/js/webauthn/webauthn-support.js
+idp.authn.WebAuthn.12.src = /net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-json.browser-ponyfill.min.js
+idp.authn.WebAuthn.12.dest = edit-webapp/js/webauthn/webauthn-json.browser-ponyfill.min.js
 
-idp.authn.WebAuthn.13.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-end.vm
-idp.authn.WebAuthn.13.dest = views/webauthn/webauthn-end.vm
+idp.authn.WebAuthn.13.src = /net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js
+idp.authn.WebAuthn.13.dest = edit-webapp/js/webauthn/webauthn-support.js
 
-idp.authn.WebAuthn.14.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
-idp.authn.WebAuthn.14.dest = views/webauthn/webauthn-management.vm
+idp.authn.WebAuthn.14.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-end.vm
+idp.authn.WebAuthn.14.dest = views/webauthn/webauthn-end.vm
 
-idp.authn.WebAuthn.15.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management-search.vm
-idp.authn.WebAuthn.15.dest = views/webauthn/webauthn-management-search.vm
+idp.authn.WebAuthn.15.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management.vm
+idp.authn.WebAuthn.15.dest = views/webauthn/webauthn-management.vm
+
+idp.authn.WebAuthn.16.src = /net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-management-search.vm
+idp.authn.WebAuthn.16.dest = views/webauthn/webauthn-management-search.vm
 
 
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckAuthenticatorPolicyTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckAuthenticatorPolicyTest.java
index 63885bf..b5721ba 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckAuthenticatorPolicyTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CheckAuthenticatorPolicyTest.java
@@ -34,11 +34,11 @@ import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
 /**
- * Test for {@link CheckAuthenticatorPolicy}.
+ * Test for {@link CheckRegistrationPolicy}.
  */
 public class CheckAuthenticatorPolicyTest extends AbstractWebAuthnTest {
     
-    private CheckAuthenticatorPolicy checkAction;
+    private CheckRegistrationPolicy checkAction;
     
     private ByteArray aaguid;
 
@@ -46,7 +46,7 @@ public class CheckAuthenticatorPolicyTest extends AbstractWebAuthnTest {
     @BeforeMethod
     public void setup() throws Exception {
         super.setup();
-        checkAction = new CheckAuthenticatorPolicy();
+        checkAction = new CheckRegistrationPolicy();
         final var context = addWebAuthnRegistrationContext();
         context.setUsername(USERNAME);
         checkAction.setWebAuthnClient(client);
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 6823f11..f9822f7 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
@@ -92,7 +92,6 @@ import net.shibboleth.idp.ui.context.RelyingPartyUIContext;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.servlet.impl.HttpServletRequestResponseContext;
 
@@ -204,21 +203,6 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
         return obj;        
     }
     
-    /**
-     * Create a credential registration with a new attestation response from the mock authenticator.
-     * 
-     * @param username the username
-     * @param display name the display name
-     * @param userHandleB64 the user handle base64 encoded
-     * @return the credential registration 
-     * 
-     * @throws Exception on error
-     */
-    protected CredentialRecord createCredentialRegistration(final String username, final String displayName,
-            final String userHandleB64) throws Exception {
-
-        return createCredentialRegistration(username, displayName, userHandleB64, null);
-    }
     
     /**
      * Create a credential registration with a new attestation response from the mock authenticator.
@@ -226,13 +210,12 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
      * @param username the username
      * @param display name the display name
      * @param userHandleB64 the user handle base64 encoded
-     * @param capabilities the capabilities of the authenticator that created this credential
      * @return the credential registration 
      * 
      * @throws Exception on error
      */
     protected CredentialRecord createCredentialRegistration(final String username, final String displayName,
-            final String userHandleB64, final Map<String,String> capabilities) throws Exception {
+            final String userHandleB64) throws Exception {
 
         final var user = UserIdentity.builder()
                 .name(username)
@@ -264,8 +247,9 @@ public abstract class AbstractWebAuthnFlowTest extends AbstractFlowTest {
                  .withCredential(credential)
                  .withCredentialNickname("nickname")
                  .withDiscoverable(Optional.of(Boolean.TRUE))
-                 .withAuthenticatorCapabilities(capabilities != null ? capabilities : CollectionSupport.emptyMap())
                  .withUserVerified(true)
+                 .withAaguid(attestation.getResponse().getParsedAuthenticatorData()
+                         .getAttestedCredentialData().get().getAaguid().getBytes())
                  .build();
          
          return reg;
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 9e9f190..c4a99ce 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
@@ -47,7 +47,8 @@ import net.shibboleth.shared.collection.Pair;
 @ContextConfiguration(
         locations = {
                 "classpath*:/META-INF/net.shibboleth.idp/postconfig.xml",
-                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans-management.xml", },
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans-management.xml", 
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
                 TestWebAuthnAdminManagementApplicationContextInitializer.class
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 d4301a4..1de07d9 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
@@ -43,7 +43,8 @@ import net.shibboleth.shared.collection.Pair;
 @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.xml", 
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
                 TestWebAuthnPasswordlessApplicationContextInitializer.class
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 f629a0e..aef5fc4 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
@@ -52,7 +52,8 @@ import net.shibboleth.shared.collection.Pair;
 @ContextConfiguration(
         locations = {
                 "classpath*:/META-INF/net.shibboleth.idp/postconfig.xml",
-                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml", },
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml", 
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
                 TestWebAuthnRegistrationApplicationContextInitializer.class
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 4fe8b63..e230574 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
@@ -47,7 +47,8 @@ import net.shibboleth.shared.collection.Pair;
 @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.xml", 
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
                 TestWebAuthn2FaApplicationContextInitializer.class
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 ed7efea..36db8d0 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
@@ -44,7 +44,8 @@ import net.shibboleth.shared.collection.Pair;
 @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.xml", 
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
                 TestWebAuthnUsernamelessApplicationContextInitializer.class
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 8e20440..511c082 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
@@ -29,7 +29,6 @@ import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 
-import net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.SecondFactorOnlyAuthenticatorInspector;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.ExtractPublicKeyCredentialAssertionFromFormRequest;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
@@ -42,7 +41,8 @@ import net.shibboleth.shared.collection.Pair;
 @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.xml", 
+                "classpath*:/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml"},
         initializers = {
                 TestWebAuthnEnvironmentApplicationContextInitializer.class,
                 TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer.class
@@ -60,13 +60,14 @@ public class TestUsernameslessFlowWithPolicy extends AbstractWebAuthnFlowTest{
         super(FLOW_ID, "proceed");
     }
     
+    /* Most the policy this is using is in the TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer 
+     * initializer.*/
     @SuppressWarnings("null")
     @Test
     public void testUsernamelessFlow_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, 
-                        Map.of(SecondFactorOnlyAuthenticatorInspector.CAPABILITY_NAME, "true"));
+                createCredentialRegistration(USERNAME, DISPLAY_NAME, USER_HANDLE_B64);
         credentialRepo.addRegistrationByUsername(USERNAME, registration);
         
         final var prc = buildProfileRequestContext(false, false, null);
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer.java
index f7b9220..4a06889 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/flow/TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer.java
@@ -42,8 +42,8 @@ public class TestWebAuthnUsernamelessWithPolicyApplicationContextInitializer
     @Override public void initialize(@Nonnull final ConfigurableApplicationContext applicationContext) {
         final MockPropertySource mock = new MockPropertySource("usernameless-mock-properties");
         mock.setProperty("idp.authn.webauthn.usernameless.enabled", "true");
-        mock.setProperty("idp.authn.webauthn.credential.policy.enabled", "true");
-        mock.setProperty("idp.authn.webauthn.registration.authenticator.policy.secondFactorOnly.enabled", "true");
+        mock.setProperty("idp.authn.webauthn.credential.policy.enabled", "true");        
+        mock.setProperty("idp.authn.webauthn.authenticator.policy.secondFactorOnlyAuthenticators", "9231fe6c-def3-4856-cbc1-e940bb035246");
         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 0542c7b..36380e6 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
@@ -334,19 +334,6 @@ public abstract class AbstractWebAuthnTest {
      * @throws Exception on error
      */
     protected CredentialRecord createCredentialRegistration() throws Exception {         
-         return createCredentialRegistration(null);
-    }
-    
-    /**
-     * Create a credential registration with a new attestation response from the mock authenticator.
-     * 
-     * @param capabilities the set of capabilities to add
-     * @return the credential registration 
-     * @throws Exception on error
-     */
-    protected CredentialRecord createCredentialRegistration(final Map<String,String> capabilities) 
-                throws Exception {
-
         final var user = UserIdentity.builder()
                 .name("jdoe")
                 .displayName("John Doe")
@@ -377,7 +364,6 @@ public abstract class AbstractWebAuthnTest {
                  .withCredential(credential)
                  .withCredentialNickname("nickname")
                  .withDiscoverable(Optional.of(Boolean.TRUE))
-                 .withAuthenticatorCapabilities(capabilities)
                  .withAaguid(attestation.getResponse().getParsedAuthenticatorData()
                          .getAttestedCredentialData().get().getAaguid().getBytes())
                  .withUserVerified(true)
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRuleTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRuleTest.java
index fe09d77..a522101 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRuleTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/ChainingCredentialPolicyRuleTest.java
@@ -24,6 +24,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
 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.CredentialRecord;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.EnhancedCredentialRecord;
 import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 
@@ -57,13 +58,14 @@ public class ChainingCredentialPolicyRuleTest extends AbstractWebAuthnTest {
             }
             
             @Override
-            public CredentialPolicyOutcome evaluate(final CredentialRecord cred, final ProfileRequestContext prc) {
+            public CredentialPolicyOutcome evaluate(
+                    final EnhancedCredentialRecord cred, final ProfileRequestContext prc) {
                 return CredentialPolicyOutcome.ACCEPT;
             }
         }));
         policy.initialize();
         
-        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        final CredentialPolicyOutcome accepted = policy.evaluate(new EnhancedCredentialRecord(registration), prc);
         assertTrue(accepted == CredentialPolicyOutcome.ACCEPT);
         
     }
@@ -78,13 +80,14 @@ public class ChainingCredentialPolicyRuleTest extends AbstractWebAuthnTest {
             }
             
             @Override
-            public CredentialPolicyOutcome evaluate(final CredentialRecord cred, final ProfileRequestContext prc) {
+            public CredentialPolicyOutcome evaluate(
+                    final EnhancedCredentialRecord cred, final ProfileRequestContext prc) {
                 return CredentialPolicyOutcome.REJECT;
             }
         }));
         policy.initialize();
         
-        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        final CredentialPolicyOutcome accepted = policy.evaluate(new EnhancedCredentialRecord(registration), prc);
         assertTrue(accepted == CredentialPolicyOutcome.REJECT);
         
     }
@@ -99,7 +102,8 @@ public class ChainingCredentialPolicyRuleTest extends AbstractWebAuthnTest {
             }
             
             @Override
-            public CredentialPolicyOutcome evaluate(final CredentialRecord cred, final ProfileRequestContext prc) {
+            public CredentialPolicyOutcome evaluate(
+                    final EnhancedCredentialRecord cred, final ProfileRequestContext prc) {
                 return CredentialPolicyOutcome.ACCEPT;
             }
         }, new CredentialPolicy() {
@@ -110,13 +114,14 @@ public class ChainingCredentialPolicyRuleTest extends AbstractWebAuthnTest {
             }
             
             @Override
-            public CredentialPolicyOutcome evaluate(final CredentialRecord cred, final ProfileRequestContext prc) {
+            public CredentialPolicyOutcome evaluate(
+                    final EnhancedCredentialRecord cred, final ProfileRequestContext prc) {
                 return CredentialPolicyOutcome.REJECT;
             }
         }));
         policy.initialize();
         
-        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        final CredentialPolicyOutcome accepted = policy.evaluate(new EnhancedCredentialRecord(registration), prc);
         assertTrue(accepted == CredentialPolicyOutcome.REJECT);
         
     }
@@ -131,13 +136,14 @@ public class ChainingCredentialPolicyRuleTest extends AbstractWebAuthnTest {
             }
             
             @Override
-            public CredentialPolicyOutcome evaluate(final CredentialRecord cred, final ProfileRequestContext prc) {
+            public CredentialPolicyOutcome evaluate(
+                    final EnhancedCredentialRecord cred, final ProfileRequestContext prc) {
                 return CredentialPolicyOutcome.IGNORE;
             }
         }));
         policy.initialize();
         
-        final CredentialPolicyOutcome accepted = policy.evaluate(registration, prc);
+        final CredentialPolicyOutcome accepted = policy.evaluate(new EnhancedCredentialRecord(registration), prc);
         assertTrue(accepted == CredentialPolicyOutcome.ACCEPT);
         
     }
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/SecondFactorOnlyCredentialPolicyRuleTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/SecondFactorOnlyCredentialPolicyRuleTest.java
deleted file mode 100644
index 07af7bc..0000000
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/policy/impl/SecondFactorOnlyCredentialPolicyRuleTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * 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 static org.testng.Assert.assertTrue;
-
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
-import net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.SecondFactorOnlyAuthenticatorInspector;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
-import net.shibboleth.idp.plugin.authn.webauthn.policy.CredentialPolicy.CredentialPolicyOutcome;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRecord;
-import net.shibboleth.shared.collection.CollectionSupport;
-import net.shibboleth.shared.component.ComponentInitializationException;
-
-/**
- * Tests for {@link SecondFactorOnlyCredentialPolicyRule}.
- */
-public class SecondFactorOnlyCredentialPolicyRuleTest  extends AbstractWebAuthnTest {
-    
-    private SecondFactorOnlyCredentialPolicyRule policy;
-    
-    private CredentialRecord credential;
-    
-    @Override
-    @BeforeMethod
-    public void setup() throws Exception {
-        super.setup();
-        policy = new SecondFactorOnlyCredentialPolicyRule();
-        policy.setId("SecondFactorOnlyPolicy");
-        credential = createCredentialRegistration();
-        
-    }
-    
-    @Test
-    public void testAllowed() throws ComponentInitializationException {        
-        policy.initialize();
-        
-        final CredentialPolicyOutcome accepted = policy.evaluate(credential, prc);
-        assertTrue(accepted == CredentialPolicyOutcome.ACCEPT);
-        
-    }
-    
-    @Test
-    public void testRejected_IsSoleFactor() throws Exception {   
-        credential = createCredentialRegistration(
-                CollectionSupport.singletonMap(SecondFactorOnlyAuthenticatorInspector.CAPABILITY_NAME, "true"));
-        policy.initialize();
-        
-        final CredentialPolicyOutcome accepted = policy.evaluate(credential, prc);
-        assertTrue(accepted == CredentialPolicyOutcome.REJECT);
-        
-    }
-}
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/MockAuthenticator.java
index 3c38725..fd9f8eb 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
@@ -92,6 +92,9 @@ public class MockAuthenticator {
      * -7 is ES256 in the IANA COSE Algorithms registry.
      */
     private static final int KEY_ALGO = -7;
+
+    /** Give this authenticator its own made up AAGUID.*/
+    private static final String AUTHENTICATOR_ATTESTATION_GUID_HEX = "9231fe6cdef34856cbc1e940bb035246";
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(MockAuthenticator.class);
@@ -183,7 +186,7 @@ public class MockAuthenticator {
         final CombinedKey createdKey = generateECDSA_256_P256_Key();
         assert createdKey != null;
         
-        final String aaguidHex = generateRandomIdentifierHex(32);
+        final String aaguidHex = AUTHENTICATOR_ATTESTATION_GUID_HEX; //generateRandomIdentifierHex(32);
         final byte[] credentialId = 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.xml b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication.xml
index b64f7ac..e8aef93 100644
--- a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication.xml
+++ b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication.xml
@@ -14,19 +14,19 @@
        default-init-method="initialize"
        default-destroy-method="destroy">
        
-    <bean id="AbstractCredentialPolicyRule" scope="prototype" abstract="true"
+    <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="SecondFactorOnlyCredentialPolicyRule" parent="AbstractCredentialPolicyRule"
-                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.SecondFactorOnlyCredentialPolicyRule"/>
+     <util:list id="shibboleth.authn.WebAuthn.ChainedCredentialPolicyList">
+        <bean id="SecondFactorOnlyCredentialPolicyRule" parent="shibboleth.authn.WebAuthn.AbstractCredentialPolicyRule"
+                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.SecondFactorOnlyCredentialPolicyRule"
+                p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.authenticator.policy.secondFactorOnlyAuthenticators:null}"/>
     </util:list>
-       
-
+    
 </beans>
diff --git a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml
index 0dd2022..465639e 100644
--- a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml
+++ b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-registration.xml
@@ -33,34 +33,4 @@
             p:allowedAuthenticators="%{idp.authn.webauthn.registration.authenticator.policy.allowedAuthenticators:null}"/>
     </util:list>    
         
-    <!-- 
-    The default list to add authenticator inspectors used during registration. The property 
-    idp.authn.webauthn.registration.authenticator.inspector.enabled must be set to true for these to take effect.
-    -->
-    <util:list id="shibboleth.authn.WebAuthn.registration.ChainedAuthenticatorInspectorList">  
-        <bean id="shibboleth.authn.WebAuthn.registration.SecondFactorOnlyInspector" scope="prototype"
-            class="net.shibboleth.idp.plugin.authn.webauthn.admin.policy.impl.SecondFactorOnlyAuthenticatorInspector"
-            p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.registration.authenticator.inspector.secondFactorOnlyAuthenticators:null}"/>
-    </util:list>
-    
-    <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
-    
-        <bean id="CapabilitiesCredentialLabeller" scope="prototype"
-            class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AuthenticatorCapabilitiesLabeller"
-            c:_0-ref="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap"/>
-        
-        <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
-    </util:list>
-    
-    <util:map id="shibboleth.authn.WebAuthn.DefaultCapabilitiesToLabelsMap">
-        <entry>
-            <key>
-                <bean parent="shibboleth.Pair"
-                    p:first="2faOnly" p:second="true"/>
-           </key>
-           <value>SecondFactorOnly</value> 
-        </entry>
-    </util:map>
-   
-
 </beans>
diff --git a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication.xml b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml
similarity index 60%
copy from webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication.xml
copy to webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml
index b64f7ac..fabd555 100644
--- a/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans-authentication.xml
+++ b/webauthn-impl/src/test/resources/net/shibboleth/idp/plugin/authn/webauthn/test-beans.xml
@@ -13,20 +13,24 @@
                            
        default-init-method="initialize"
        default-destroy-method="destroy">
-       
-    <bean id="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.
-    
+    The default credential labeller list. Use during registration or management flows to label credential for display
+    purposes only. 
     -->
-    <util:list id="shibboleth.authn.WebAuthn.ChainedCredentialPolicyList">
-        <bean id="SecondFactorOnlyCredentialPolicyRule" parent="AbstractCredentialPolicyRule"
-                class="net.shibboleth.idp.plugin.authn.webauthn.policy.impl.SecondFactorOnlyCredentialPolicyRule"/>
+    <util:list id="shibboleth.authn.WebAuthn.CredentialLabellerList">
+    
+        <bean id="CapabilitiesCredentialLabeller" scope="prototype"
+            class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.SecondFactorOnlyCredentialLabeller"
+            p:secondFactorOnlyAuthenticators="%{idp.authn.webauthn.authenticator.policy.secondFactorOnlyAuthenticators:null}"
+            p:label="SecondFactorOnly"/>
+        
+        <bean id="PasskeyCredentialLabeller" class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.PasskeyCredentialLabeller"/>
     </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