[java-idp-plugin-webauthn] branch main updated: JWEBAUTHN-16 - Add auditing

Phil Smart philip.smart at jisc.ac.uk
Fri Jul 19 09:34:31 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=78d9804a4d72662985531bc29b4fb7a724fc0f79

The following commit(s) were added to refs/heads/main by this push:
     new 78d9804  JWEBAUTHN-16 - Add auditing
78d9804 is described below

commit 78d9804a4d72662985531bc29b4fb7a724fc0f79
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Jul 19 10:34:27 2024 +0100

    JWEBAUTHN-16 - Add auditing
    
     - Add auditing support to management beans. Using a new
    AbstractWebAuthnAuditingAction.
     - Refactor actions to support a simplified parent hierarchy that can
    include the new auditing action.
    
    https://shibboleth.atlassian.net/browse/JWEBAUTHN-16
---
 .../audit/AbstractWebAuthnAuditExtractor.java      |   6 +-
 ... AbstractWebAuthnManagementAuditExtractor.java} |  31 ++--
 ...bstractWebAuthnRegistrationAuditExtractor.java} |  31 ++--
 .../authn/webauthn/audit/WebAuthnAuditFields.java  |  15 ++
 .../impl/AbstractWebAuthnManagementAction.java     | 139 --------------
 .../impl/AbstractWebAuthnRegistrationAction.java   | 139 --------------
 .../impl/AddAttestationConveyancePreference.java   |   5 +-
 .../AddAuthenticatorAttachmentRequirement.java     |   5 +-
 .../authn/webauthn/admin/impl/AddDisplayName.java  |   9 +-
 .../admin/impl/AddResidentKeyRequirement.java      |   5 +-
 .../authn/webauthn/admin/impl/AddUserId.java       |  23 ++-
 .../admin/impl/AdminDeletePublicKeyCredential.java |  36 +++-
 .../CreatePublicKeyCredentialCreationOptions.java  |  11 +-
 .../admin/impl/DeletePublicKeyCredential.java      |  36 +++-
 ...licKeyCredentialAttestationFromFormRequest.java |  12 +-
 .../impl/ExtractUsernameSearchFromFormRequest.java |   7 +-
 .../admin/impl/LookupCredentialsForUser.java       |  29 ++-
 .../admin/impl/StorePublicKeyCredential.java       |  31 +++-
 .../ValidateAuthenticatorAttestationResponse.java  |  10 +-
 .../audit/impl/AbstractWebAuthnAuditingAction.java | 205 +++++++++++++++++++++
 .../WebAuthnCredentialToRemoveAuditExtractor.java  |  59 ++++++
 .../impl/WebAuthnSearchUserAuditExtractor.java     |  47 +++++
 .../WebAuthnUserCredentialAddedAuditExtractor.java |  63 +++++++
 ...bAuthnUserCredentialToRemoveAuditExtractor.java |  59 ++++++
 ...tionAction.java => AbstractWebAuthnAction.java} | 145 ++++++++-------
 .../webauthn/impl/AbstractWebAuthnBaseAction.java  | 142 --------------
 .../impl/AddUserVerificationRequirement.java       |   6 +-
 .../authn/webauthn/impl/BaseWebAuthnAction.java    | 115 ------------
 .../CreatePublicKeyCredentialRequestOptions.java   |  10 +-
 .../impl/EnsureAllowedCredentialsIsEmpty.java      |  10 +-
 ...ublicKeyCredentialAssertionFromFormRequest.java |  11 +-
 .../webauthn/impl/GenerateServerChallenge.java     |   6 +-
 .../webauthn/impl/LookupRegisteredCredentials.java |  26 ++-
 .../LookupRegisteredCredentialsFromUserHandle.java |  33 ++--
 .../webauthn-management-beans.xml                  |  42 ++++-
 .../webauthn-registration-beans.xml                |  52 +++++-
 .../authn/WebAuthn/webauthn-abstract-beans.xml     |  45 ++++-
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |  12 +-
 .../authn/webauthn/conf/authn/webauthn.properties  |   7 +
 .../authn/webauthn/views/webauthn-register.vm      |   2 +-
 40 files changed, 977 insertions(+), 700 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java
index d2ba8c5..7476204 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java
@@ -37,7 +37,11 @@ public abstract class AbstractWebAuthnAuditExtractor<T> implements Function<Prof
     @Nonnull 
     private final Function<ProfileRequestContext,BaseWebAuthnContext> webAuthnBaseContextLookupStrategy;
     
-    /** Constructor.*/
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the {@link BaseWebAuthnContext}.
+     */
     protected AbstractWebAuthnAuditExtractor(
             @Nonnull final Function<ProfileRequestContext,BaseWebAuthnContext> strategy) {
         webAuthnBaseContextLookupStrategy = Constraint.isNotNull(strategy, 
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnManagementAuditExtractor.java
similarity index 56%
copy from webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java
copy to webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnManagementAuditExtractor.java
index d2ba8c5..655b263 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnManagementAuditExtractor.java
@@ -22,26 +22,30 @@ import javax.annotation.concurrent.ThreadSafe;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
 import net.shibboleth.shared.logic.Constraint;
 
 /** 
- * An abstract audit extractor function to extract values directly from various WebAuthn contexts.
+ * An abstract audit extractor function to extract values directly from the WebAuthn management context.
  * 
  * @param <T> the type to extract
  */
 @ThreadSafe
-public abstract class AbstractWebAuthnAuditExtractor<T> implements Function<ProfileRequestContext, T> {
+public abstract class AbstractWebAuthnManagementAuditExtractor<T> implements Function<ProfileRequestContext, T> {
     
-    /** Lookup strategy to locate the WebAuthn base context. */
+    /** Lookup strategy to locate the WebAuthn management context. */
     @Nonnull 
-    private final Function<ProfileRequestContext,BaseWebAuthnContext> webAuthnBaseContextLookupStrategy;
+    private final Function<ProfileRequestContext,WebAuthnManagementContext> webAuthnManagementContextLookupStrategy;
     
-    /** Constructor.*/
-    protected AbstractWebAuthnAuditExtractor(
-            @Nonnull final Function<ProfileRequestContext,BaseWebAuthnContext> strategy) {
-        webAuthnBaseContextLookupStrategy = Constraint.isNotNull(strategy, 
-                "BaseWebAuthnContext lookup strategy can not be null");
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the {@link WebAuthnManagementContext}.
+     */
+    protected AbstractWebAuthnManagementAuditExtractor(
+            @Nonnull final Function<ProfileRequestContext,WebAuthnManagementContext> strategy) {
+        webAuthnManagementContextLookupStrategy = Constraint.isNotNull(strategy, 
+                "WebAuthnManagementContext lookup strategy can not be null");
     }
     
     
@@ -51,7 +55,8 @@ public abstract class AbstractWebAuthnAuditExtractor<T> implements Function<Prof
         if (profileRequestContext == null) {
             return null;
         }
-        final BaseWebAuthnContext webAuthnContext = webAuthnBaseContextLookupStrategy.apply(profileRequestContext);
+        final WebAuthnManagementContext webAuthnContext = 
+                webAuthnManagementContextLookupStrategy.apply(profileRequestContext);
         if (webAuthnContext == null) {
             return null;
         } 
@@ -61,10 +66,10 @@ public abstract class AbstractWebAuthnAuditExtractor<T> implements Function<Prof
     /**
      * Implemented to perform the actual lookup.
      * 
-     * @param context the base WebAuthn context
+     * @param context the base WebAuthn management context
      * 
      * @return lookup value or {@code null} if not found
      */
-    @Nullable protected abstract T doLookup(@Nonnull final BaseWebAuthnContext context);
+    @Nullable protected abstract T doLookup(@Nonnull final WebAuthnManagementContext context);
 
 }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnRegistrationAuditExtractor.java
similarity index 55%
copy from webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java
copy to webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnRegistrationAuditExtractor.java
index d2ba8c5..c657a9f 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnAuditExtractor.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/AbstractWebAuthnRegistrationAuditExtractor.java
@@ -22,26 +22,30 @@ import javax.annotation.concurrent.ThreadSafe;
 
 import org.opensaml.profile.context.ProfileRequestContext;
 
-import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 import net.shibboleth.shared.logic.Constraint;
 
 /** 
- * An abstract audit extractor function to extract values directly from various WebAuthn contexts.
+ * An abstract audit extractor function to extract values directly from the WebAuthn registration context.
  * 
  * @param <T> the type to extract
  */
 @ThreadSafe
-public abstract class AbstractWebAuthnAuditExtractor<T> implements Function<ProfileRequestContext, T> {
+public abstract class AbstractWebAuthnRegistrationAuditExtractor<T> implements Function<ProfileRequestContext, T> {
     
-    /** Lookup strategy to locate the WebAuthn base context. */
+    /** Lookup strategy to locate the WebAuthn registration context. */
     @Nonnull 
-    private final Function<ProfileRequestContext,BaseWebAuthnContext> webAuthnBaseContextLookupStrategy;
+    private final Function<ProfileRequestContext,WebAuthnRegistrationContext> webAuthnRegistrationContextLookupStrategy;
     
-    /** Constructor.*/
-    protected AbstractWebAuthnAuditExtractor(
-            @Nonnull final Function<ProfileRequestContext,BaseWebAuthnContext> strategy) {
-        webAuthnBaseContextLookupStrategy = Constraint.isNotNull(strategy, 
-                "BaseWebAuthnContext lookup strategy can not be null");
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the {@link WebAuthnRegistrationContext}.
+     */
+    protected AbstractWebAuthnRegistrationAuditExtractor(
+            @Nonnull final Function<ProfileRequestContext,WebAuthnRegistrationContext> strategy) {
+        webAuthnRegistrationContextLookupStrategy = Constraint.isNotNull(strategy, 
+                "WebAuthnRegistrationContext lookup strategy can not be null");
     }
     
     
@@ -51,7 +55,8 @@ public abstract class AbstractWebAuthnAuditExtractor<T> implements Function<Prof
         if (profileRequestContext == null) {
             return null;
         }
-        final BaseWebAuthnContext webAuthnContext = webAuthnBaseContextLookupStrategy.apply(profileRequestContext);
+        final WebAuthnRegistrationContext webAuthnContext = 
+                webAuthnRegistrationContextLookupStrategy.apply(profileRequestContext);
         if (webAuthnContext == null) {
             return null;
         } 
@@ -61,10 +66,10 @@ public abstract class AbstractWebAuthnAuditExtractor<T> implements Function<Prof
     /**
      * Implemented to perform the actual lookup.
      * 
-     * @param context the base WebAuthn context
+     * @param context the WebAuthn registration context
      * 
      * @return lookup value or {@code null} if not found
      */
-    @Nullable protected abstract T doLookup(@Nonnull final BaseWebAuthnContext context);
+    @Nullable protected abstract T doLookup(@Nonnull final WebAuthnRegistrationContext context);
 
 }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/WebAuthnAuditFields.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/WebAuthnAuditFields.java
index 79de0e4..68eebfd 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/WebAuthnAuditFields.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/WebAuthnAuditFields.java
@@ -36,5 +36,20 @@ public final class WebAuthnAuditFields {
     
     /** What mode produced the final result e.g. 'passwordless'. */
     @Nonnull @NotEmpty public static final String FLOW_MODE = "WebAuthnFM";
+    
+    /** The user the admin performed an action on. */
+    @Nonnull @NotEmpty public static final String ADMIN_AFFECTED_USER = "WebAuthnAdminAU";
+    
+    /** The outcome of an action, typically success or failure.*/
+    @Nonnull @NotEmpty public static final String ACTION_OUTCOME = "WebAuthnAdminAO";
+    
+    /** The type of action performed. Controlled by the action, not by an audit extractor.*/
+    @Nonnull @NotEmpty public static final String ACTION = "WebAuthnAdminAction";
+    
+    /** The Id of the credential that has been removed.*/
+    @Nonnull @NotEmpty public static final String CRED_REMOVED = "WebAuthnAdminCR";
+    
+    /** The Id of the credential that has been added.*/
+    @Nonnull @NotEmpty public static final String CRED_ADDED = "WebAuthnAdminCA";
 
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnManagementAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnManagementAction.java
deleted file mode 100644
index 6e8e8b0..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnManagementAction.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.BaseWebAuthnAction;
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * <p>A base class for WebAuthn administrative management function actions.</p>
- * 
- * <p>In addition to the work performed by {@link AbstractProfileAction}, this action also looks up
- * and makes available the {@link WebAuthnManagementContext}.</p>
- * 
- * <p>WebAuthn administrative action implementations should override the
- * {@link #doExecute(ProfileRequestContext, WebAuthnManagementContext)} method.</p>
- * 
- * @event {@link EventIds#INVALID_PROFILE_CTX}
- * @pre <pre>ProfileRequestContext.getSubcontext(ProfileRequestContext.class) != null</pre>
- * @post <pre>AuthenticationContext.getSubcontext(WebAuthnManagementContext.class) != null</pre>
- */
-public abstract class AbstractWebAuthnManagementAction extends BaseWebAuthnAction {
-    
-    /** Class logger. */
-    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(AbstractWebAuthnManagementAction.class);
-    
-    /** Lookup strategy to locate the webauthn management context. */
-    @Nonnull 
-    private Function<ProfileRequestContext,WebAuthnManagementContext> webauthnManagementContextLookupStrategy;
-    
-    /** The WebAuthn management Context.*/
-    @NonnullBeforeExec private WebAuthnManagementContext webauthnManagementContext;
-    
-    /** Constructor.*/
-    protected AbstractWebAuthnManagementAction() {
-        //prc -> WebAuthnContext
-        webauthnManagementContextLookupStrategy = new ChildContextLookup<>(WebAuthnManagementContext.class);
-    }
-    
-    /**
-     * Set WebAuthn management context lookup strategy to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setWebauthnManagementContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,WebAuthnManagementContext> strategy) {
-        checkSetterPreconditions();
-
-        webauthnManagementContextLookupStrategy = 
-                Constraint.isNotNull(strategy, "WebauthnManagementContextLookupStrategy strategy cannot be null");
-    }
-
-    
-    /** {@inheritDoc} */
-    @Override
-    protected final boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-      
-        webauthnManagementContext = webauthnManagementContextLookupStrategy.apply(profileRequestContext);
-        if (webauthnManagementContext == null) {
-            log.warn("{} No WebAuthn management context returned by lookup strategy",getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-            
-        }
-        assert webauthnManagementContext != null;
-        return doPreExecute(profileRequestContext, webauthnManagementContext);
-    }
-    
-    /**
-     * Delegates to {@link #doExecute(ProfileRequestContext, WebAuthnManagementContext)} to perform the 
-     * actual action. Implementations can not override this method.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     */
-    @Override
-    protected final void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) { 
-        assert webauthnManagementContext != null;
-        doExecute(profileRequestContext, webauthnManagementContext);
-    }
-    
-    /**
-     * Performs this admin action's pre-execute step. Default implementation just returns true.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     * @param context the WebAuthn management context
-     * 
-     * @return true iff execution should continue
-     */
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnManagementContext context) {
-        return true;
-    }
-    
-    /**
-     * Performs this webauthn authentication action using the supplied WebAuthn management context. Implementations
-     * should override this method.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     * @param context the WebAuthn management context
-     */
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnManagementContext context) {
-        
-    }
-
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnRegistrationAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnRegistrationAction.java
deleted file mode 100644
index ba61bd5..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AbstractWebAuthnRegistrationAction.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.BaseWebAuthnAction;
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * <p>A base class for WebAuthn registration related administration actions.</p>
- * 
- * <p>In addition to the work performed by {@link AbstractProfileAction}, this action also looks up
- * and makes available the {@link WebAuthnRegistrationContext}.</p>
- * 
- * <p>WebAuthn registration action implementations should override the
- * {@link #doExecute(ProfileRequestContext, WebAuthnRegistrationContext)} method.</p>
- * 
- * @event {@link EventIds#INVALID_PROFILE_CTX}
- * @pre <pre>ProfileRequestContext.getSubcontext(ProfileRequestContext.class) != null</pre>
- * @post <pre>AuthenticationContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
- */
-public abstract class AbstractWebAuthnRegistrationAction extends BaseWebAuthnAction {
-    
-    /** Class logger. */
-    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(AbstractWebAuthnRegistrationAction.class);
-    
-    /** Lookup strategy to locate the webauthn registration context. */
-    @Nonnull 
-    private Function<ProfileRequestContext,WebAuthnRegistrationContext> webauthnRegistrationContextLookupStrategy;
-    
-    /** The WebAuthn registration Context.*/
-    @NonnullBeforeExec private WebAuthnRegistrationContext webauthnRegistrationContext;
-
-    
-    /** Constructor.*/
-    protected AbstractWebAuthnRegistrationAction() {
-        //prc -> WebAuthnContext
-        webauthnRegistrationContextLookupStrategy = new ChildContextLookup<>(WebAuthnRegistrationContext.class);
-    }
-
-    /**
-     * Set WebAuthn registration context lookup strategy to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setWebauthnRegistrationContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,WebAuthnRegistrationContext> strategy) {
-        checkSetterPreconditions();
-
-        webauthnRegistrationContextLookupStrategy = 
-                Constraint.isNotNull(strategy, "WebauthnContextLookuplookup strategy cannot be null");
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected final boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-      
-        webauthnRegistrationContext = webauthnRegistrationContextLookupStrategy.apply(profileRequestContext);
-        if (webauthnRegistrationContext == null) {
-            log.warn("{} No WebAuthn registration context returned by lookup strategy",getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-            
-        }  
-        assert webauthnRegistrationContext != null;
-        return doPreExecute(profileRequestContext, webauthnRegistrationContext);
-    }
-    
-    /**
-     * Delegates to {@link #doExecute(ProfileRequestContext, WebAuthnRegistrationContext)} to perform the 
-     * actual action. Implementations can not override this method.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     */
-    @Override
-    protected final void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        assert webauthnRegistrationContext != null;
-        doExecute(profileRequestContext, webauthnRegistrationContext);
-    }
-    
-    /**
-     * Performs this registration action's pre-execute step. Default implementation just returns true.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     * @param context the WebAuthn registration context
-     * 
-     * @return true iff execution should continue
-     */
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnRegistrationContext context) {
-        return true;
-    }
-    
-    /**
-     * Performs this webauthn registration action using the supplied webauthn context. Implementations
-     * should override this method.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     * @param context the WebAuthn registration context
-     */
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnRegistrationContext context) {
-        
-    }
-
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAttestationConveyancePreference.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAttestationConveyancePreference.java
index ce7085c..58b0005 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAttestationConveyancePreference.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAttestationConveyancePreference.java
@@ -18,6 +18,7 @@ import java.util.stream.Stream;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
@@ -25,6 +26,7 @@ import com.yubico.webauthn.data.AttestationConveyancePreference;
 
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.ConstraintViolationException;
@@ -38,7 +40,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @post <pre>WebAuthnRegistrationContext.getAttestationConveyancePreference() != null</pre>
  * 
  */
-public class AddAttestationConveyancePreference extends AbstractWebAuthnRegistrationAction {
+public class AddAttestationConveyancePreference extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AddAttestationConveyancePreference.class);
@@ -48,6 +50,7 @@ public class AddAttestationConveyancePreference extends AbstractWebAuthnRegistra
     
     /** Constructor.*/
     public AddAttestationConveyancePreference() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
         attestationConveyancePreference = AttestationConveyancePreference.NONE;
     }
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAuthenticatorAttachmentRequirement.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAuthenticatorAttachmentRequirement.java
index c3ad7a8..bba98a8 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAuthenticatorAttachmentRequirement.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddAuthenticatorAttachmentRequirement.java
@@ -19,12 +19,14 @@ import java.util.stream.Stream;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.yubico.webauthn.data.AuthenticatorAttachment;
 
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.ConstraintViolationException;
@@ -37,7 +39,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post <pre>WebAuthnRegistrationContext.getAuthenticatorAttachmentRequirement() != null</pre>
  */
-public class AddAuthenticatorAttachmentRequirement extends AbstractWebAuthnRegistrationAction {
+public class AddAuthenticatorAttachmentRequirement extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AddAuthenticatorAttachmentRequirement.class);
@@ -47,6 +49,7 @@ public class AddAuthenticatorAttachmentRequirement extends AbstractWebAuthnRegis
     
     /** Constructor.*/
     public AddAuthenticatorAttachmentRequirement() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
         authenticatorAttachmentRequirement = null;
     }
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddDisplayName.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddDisplayName.java
index 5de9b5d..a1e2e87 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddDisplayName.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddDisplayName.java
@@ -22,12 +22,14 @@ import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.component.ComponentInitializationException;
@@ -41,7 +43,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post a displayName is added to the registration context
  */
-public class AddDisplayName extends AbstractWebAuthnRegistrationAction {
+public class AddDisplayName extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AddDisplayName.class);
@@ -51,6 +53,11 @@ public class AddDisplayName extends AbstractWebAuthnRegistrationAction {
     
     /** The stashed username.*/
     @NonnullBeforeExec private String username;
+    
+    /** Constructor.*/
+    protected AddDisplayName() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+    }
         
     /**
      * Set the strategy used to generate the user.displayName.
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddResidentKeyRequirement.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddResidentKeyRequirement.java
index 94e5724..9c202a8 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddResidentKeyRequirement.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddResidentKeyRequirement.java
@@ -18,12 +18,14 @@ import java.util.stream.Stream;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.yubico.webauthn.data.ResidentKeyRequirement;
 
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.ConstraintViolationException;
@@ -35,7 +37,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post <pre>WebAuthnRegistrationContext.getResidentKeyRequirement() != null</pre>
  */
-public class AddResidentKeyRequirement extends AbstractWebAuthnRegistrationAction {
+public class AddResidentKeyRequirement extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AddResidentKeyRequirement.class);
@@ -45,6 +47,7 @@ public class AddResidentKeyRequirement extends AbstractWebAuthnRegistrationActio
     
     /** Constructor.*/
     public AddResidentKeyRequirement() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
         residentKeyRequirement = ResidentKeyRequirement.PREFERRED;
     }
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java
index a78adc4..468b0ae 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AddUserId.java
@@ -22,6 +22,7 @@ import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -30,9 +31,13 @@ import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -53,7 +58,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post a user ID is added to the registration context
  */
-public class AddUserId extends AbstractWebAuthnRegistrationAction {
+public class AddUserId extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AddUserId.class);
@@ -64,8 +69,22 @@ public class AddUserId extends AbstractWebAuthnRegistrationAction {
     /** The stashed username.*/
     @NonnullBeforeExec private String username;
     
+    /** The credential repository to use.*/
+    @NonnullAfterInit private StorageServiceCredentialRepository repository;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }
+    
     /** Constructor. */
     public AddUserId() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
         userIdGeneratorStrategy = new RandomUserIdGenerator();
     }
     
@@ -105,7 +124,7 @@ public class AddUserId extends AbstractWebAuthnRegistrationAction {
             @Nonnull final WebAuthnRegistrationContext context) {
         
         // Any existing user handle is the user.id  
-        final Optional<ByteArray> existingUserHandle = getCredentialRepository().getUserHandleForUsername(username);
+        final Optional<ByteArray> existingUserHandle = repository.getUserHandleForUsername(username);
               
         if (existingUserHandle.isPresent()) {
             final byte[] handleAsBytes = existingUserHandle.get().getBytes();
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AdminDeletePublicKeyCredential.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AdminDeletePublicKeyCredential.java
index bc23277..a97a09b 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AdminDeletePublicKeyCredential.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/AdminDeletePublicKeyCredential.java
@@ -19,6 +19,7 @@ package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -26,9 +27,13 @@ import org.slf4j.Logger;
 import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.audit.impl.AbstractWebAuthnAuditingAction;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -39,11 +44,30 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnManagementContext.class) != null</pre>
  * @post a credential is removed from credential repository
  */
-public class AdminDeletePublicKeyCredential extends AbstractWebAuthnManagementAction {
+//TODO merge with the non admin delete action?
+public class AdminDeletePublicKeyCredential extends AbstractWebAuthnAuditingAction<WebAuthnManagementContext> {
 
     /** Class logger. */
     @Nonnull
-    private final Logger log = LoggerFactory.getLogger(AdminDeletePublicKeyCredential.class);    
+    private final Logger log = LoggerFactory.getLogger(AdminDeletePublicKeyCredential.class); 
+    
+    /** The credential repository to use.*/
+    @NonnullAfterInit private StorageServiceCredentialRepository repository;
+    
+    /** Constructor.*/
+    public AdminDeletePublicKeyCredential() {
+        super(new ChildContextLookup<>(WebAuthnManagementContext.class));
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }
 
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -63,7 +87,7 @@ public class AdminDeletePublicKeyCredential extends AbstractWebAuthnManagementAc
             return;
         }
 
-        final boolean removed = getCredentialRepository().removeRegistrationByUsernameAndCredentialId(
+        final boolean removed = repository.removeRegistrationByUsernameAndCredentialId(
                 credentialUsername, new ByteArray(credentialId));
         
         if (log.isDebugEnabled()) {
@@ -74,7 +98,11 @@ public class AdminDeletePublicKeyCredential extends AbstractWebAuthnManagementAc
                 // Do nothing if the encoding fails.
             }
         }        
-        
+        if (removed) {
+            auditSuccess(profileRequestContext, "credential-removed");
+        } else {
+            auditFailure(profileRequestContext, "credential-removed");
+        }
         // Remove the key to be deleted in-case the context state is re-used
         context.setCredentialIdToRemove(null);
     }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
index 8705f05..d63809f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/CreatePublicKeyCredentialCreationOptions.java
@@ -21,6 +21,7 @@ import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -37,6 +38,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventI
 import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.exception.WebAuthnAuthenticationClientException;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.shared.logic.ConstraintViolationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -49,10 +51,15 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post a PublicKeyCredentialCreationOptions is added to the registration context
  */
-public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnRegistrationAction {
+public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
     
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(CreatePublicKeyCredentialCreationOptions.class);     
+    @Nonnull private final Logger log = LoggerFactory.getLogger(CreatePublicKeyCredentialCreationOptions.class); 
+    
+    /** Constructor.*/
+    protected CreatePublicKeyCredentialCreationOptions() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+    }
     
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredential.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredential.java
index ab00805..95cbd16 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredential.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/DeletePublicKeyCredential.java
@@ -21,6 +21,7 @@ import java.util.Optional;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -28,8 +29,12 @@ import org.slf4j.Logger;
 import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.audit.impl.AbstractWebAuthnAuditingAction;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -39,11 +44,29 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post a credential is removed from credential repository
  */
-public class DeletePublicKeyCredential extends AbstractWebAuthnRegistrationAction {
+public class DeletePublicKeyCredential extends AbstractWebAuthnAuditingAction<WebAuthnRegistrationContext> {
 
     /** Class logger. */
     @Nonnull
-    private final Logger log = LoggerFactory.getLogger(DeletePublicKeyCredential.class);    
+    private final Logger log = LoggerFactory.getLogger(DeletePublicKeyCredential.class);  
+    
+    /** The credential repository to use.*/
+    @NonnullAfterInit private StorageServiceCredentialRepository repository;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }
+    
+    /** Constructor.*/
+    protected DeletePublicKeyCredential() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+    }
 
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -67,16 +90,21 @@ public class DeletePublicKeyCredential extends AbstractWebAuthnRegistrationActio
         }
         
         final Optional<CredentialRegistration> credential = 
-                getCredentialRepository().getRegistrationByUsernameAndCredentialId(
+                repository.getRegistrationByUsernameAndCredentialId(
                         username, new ByteArray(credentialId));
         
         if (credential.isEmpty()) {
             // This is not an error
             log.debug("{} Unable to find credential to remove, nothing to remove", getLogPrefix());            
         } else {
-            final boolean removed = getCredentialRepository().removeRegistrationByUsername(username, credential.get());
+            final boolean removed = repository.removeRegistrationByUsername(username, credential.get());
             log.debug("{} Credential '{}' {} removed", getLogPrefix(), credential.get().getCredentialIdBase64Url(),
                     removed ? "was" : "was not");
+            if(removed) {
+                auditSuccess(profileRequestContext, "credential-removed");
+            } else {
+                auditFailure(profileRequestContext, "credential-removed");
+            }
         }
         
         // Remove the key to be deleted in-case the context state is re-used
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java
index 0bfe579..2b1b1f4 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractPublicKeyCredentialAttestationFromFormRequest.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -33,6 +34,7 @@ import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -41,14 +43,15 @@ import net.shibboleth.shared.primitive.StringSupport;
 
 /**
  * An action that extracts the PublicKeyCredential containing the authenticator attestation response from the incoming
- * HTTP request. Also extracts the user entered credential nickname. 
+ * HTTP request. Also extracts the user entered credential nickname. Failure to find either results in an error event.
  * 
  * @event {WebAuthnRegistrationEventIds#INVALID_REGISTRATION}
  * @event {AuthnEventIds#NO_CREDENTIALS}
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
- * @post add an AuthenticatorAttestationResponse to the registration context
+ * @post add an AuthenticatorAttestationResponse and Credential Nickname to the registration context
  */
-public class ExtractPublicKeyCredentialAttestationFromFormRequest extends AbstractWebAuthnRegistrationAction {
+public class ExtractPublicKeyCredentialAttestationFromFormRequest 
+                    extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
 
     /** Default public key credential attestation parameter name. */
     @Nonnull @NotEmpty public static final String DEFAULT_PARAMETER_NAME = "publicKeyCredential";
@@ -68,6 +71,7 @@ public class ExtractPublicKeyCredentialAttestationFromFormRequest extends Abstra
     
     /** Constructor. */
     public ExtractPublicKeyCredentialAttestationFromFormRequest() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
         publicKeyCredentialAttestationParameterName = DEFAULT_PARAMETER_NAME;
         credentialNicknameParameterName = DEFAULT_NICKNAME_FIELD_NAME;
     }
@@ -130,7 +134,7 @@ public class ExtractPublicKeyCredentialAttestationFromFormRequest extends Abstra
             context.setCredentialNickname(credNickname);
         } catch (final IOException e) {
             log.debug("{} Could not parse PublicKeyCredential response from request parameter '{}'", getLogPrefix(),
-                    credentialNicknameParameterName, e);
+                    publicKeyCredentialAttestationParameterName, e);
             ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
             return;
         }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractUsernameSearchFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractUsernameSearchFromFormRequest.java
index af6b960..83d5ce4 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractUsernameSearchFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractUsernameSearchFromFormRequest.java
@@ -19,6 +19,7 @@ package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -26,6 +27,7 @@ import org.slf4j.Logger;
 import jakarta.servlet.http.HttpServletRequest;
 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.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
@@ -42,7 +44,7 @@ import net.shibboleth.shared.primitive.StringSupport;
  * @post add the parameter value to the context
  */
 //TODO just make this generic? would need a consumer to set the information?
-public class ExtractUsernameSearchFromFormRequest extends AbstractWebAuthnManagementAction {
+public class ExtractUsernameSearchFromFormRequest extends AbstractWebAuthnAction<WebAuthnManagementContext> {
 
     /** Default credential Id parameter name. */
     @Nonnull @NotEmpty public static final String DEFAULT_PARAMETER_NAME = "username_search";
@@ -54,7 +56,8 @@ public class ExtractUsernameSearchFromFormRequest extends AbstractWebAuthnManage
     @NonnullAfterInit @NotEmpty private String requestParameterToExtract;
 
     /** Constructor. */
-    public ExtractUsernameSearchFromFormRequest() {       
+    public ExtractUsernameSearchFromFormRequest() {      
+        super(new ChildContextLookup<>(WebAuthnManagementContext.class));
         requestParameterToExtract = DEFAULT_PARAMETER_NAME;
     }
     
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 6c31eb4..0b2942e 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
@@ -18,13 +18,18 @@ import java.util.Collection;
 
 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.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.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
@@ -36,11 +41,29 @@ import net.shibboleth.shared.primitive.StringSupport;
  * @post BaseWebAuthnContext.setExistingCredentials() is either null if no existing credentials are found, or contains
  * the credentials from the credential repository
  */
-public class LookupCredentialsForUser extends AbstractWebAuthnManagementAction {
+public class LookupCredentialsForUser extends AbstractWebAuthnAction<WebAuthnManagementContext> {
 
     /** Class logger. */
     @Nonnull
-    private final Logger log = LoggerFactory.getLogger(LookupCredentialsForUser.class);    
+    private final Logger log = LoggerFactory.getLogger(LookupCredentialsForUser.class); 
+    
+    /** The credential repository to use.*/
+    @NonnullAfterInit private StorageServiceCredentialRepository repository;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }
+    
+    /** Constructor. */
+    public LookupCredentialsForUser() {      
+        super(new ChildContextLookup<>(WebAuthnManagementContext.class));
+    }
 
     /** {@inheritDoc} */
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -54,7 +77,7 @@ public class LookupCredentialsForUser extends AbstractWebAuthnManagementAction {
             log.trace("{} Finding registered credentials for '{}'",getLogPrefix(), userToSearchFor);
             assert userToSearchFor != null;
             final Collection<CredentialRegistration> credentials = 
-                    getCredentialRepository().getRegistrationsByUsername(userToSearchFor);      
+                    repository.getRegistrationsByUsername(userToSearchFor);      
             log.debug("{} Found '{}' credentials", getLogPrefix(), credentials.size());
             context.setFoundCredentials(credentials);
         }
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 aeead6b..004ffc7 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
@@ -24,6 +24,7 @@ import java.util.TreeSet;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -37,13 +38,17 @@ import com.yubico.webauthn.data.UserIdentity;
 
 import net.shibboleth.idp.plugin.authn.webauthn.admin.RegistrationResult;
 import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.audit.impl.AbstractWebAuthnAuditingAction;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullElements;
 import net.shibboleth.shared.annotation.constraint.NotLive;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
 import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -57,10 +62,28 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  * @post the credential from the registration context is added to the credential repository
  */
-public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction {
+public class StorePublicKeyCredential extends AbstractWebAuthnAuditingAction<WebAuthnRegistrationContext> {
 
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(StorePublicKeyCredential.class);
+    @Nonnull private final Logger log = LoggerFactory.getLogger(StorePublicKeyCredential.class);   
+    
+    /** The credential repository to use.*/
+    @NonnullAfterInit private StorageServiceCredentialRepository repository;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }    
+    
+    /** Constructor. */
+    protected StorePublicKeyCredential() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+    }
 
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -119,7 +142,7 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
                     .withUserVerified(registrationResult.isUserVerified())
                     .build();
             
-            getCredentialRepository().addRegistrationByUsername(username, registration);  
+            repository.addRegistrationByUsername(username, registration);  
             
             if (log.isInfoEnabled()) {
             
@@ -136,12 +159,14 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
                         getLogPrefix(), username, userIdBase64, registrationResult.getKeyId().getId().getBase64Url(),
                         registrationResult.isDiscoverable().isPresent() ? registrationResult.isDiscoverable() : 
                             "unknown", registrationResult.isUserVerified());
+                auditSuccess(profileRequestContext, "credential-added");
             }
 
         } catch (final Exception e) {
             log.error("{} Unable to store registration for key '{}'",getLogPrefix(), 
                     registrationResult.getKeyId().getId().getBase64Url(), e);
             ActionSupport.buildEvent(profileRequestContext, WebAuthnRegistrationEventIds.INVALID_REGISTRATION);
+            auditFailure(profileRequestContext, "credential-added");
             return;
         }
         
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java
index 618f498..b5e45d3 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ValidateAuthenticatorAttestationResponse.java
@@ -19,6 +19,7 @@ package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -34,6 +35,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.admin.WebAuthnRegistrationEventI
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationErrorContext;
 import net.shibboleth.idp.plugin.authn.webauthn.exception.RegistrationFailureException;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -44,7 +46,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @event {WebAuthnRegistrationEventIds#INVALID_REGISTRATION}
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnRegistrationContext.class) != null</pre>
  */
-public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnRegistrationAction {
+public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnAction<WebAuthnRegistrationContext> {
 
     /** Class logger. */
     @Nonnull
@@ -55,8 +57,12 @@ public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnRe
     
     /** The stashed public key credential authenticator attestation response.*/
     @NonnullBeforeExec
-    private PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation;
+    private PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation;    
 
+    /** Constructor.*/
+    public ValidateAuthenticatorAttestationResponse() {
+        super(new ChildContextLookup<>(WebAuthnRegistrationContext.class));
+    }
     
     /** {@inheritDoc} */
     @Override
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/AbstractWebAuthnAuditingAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/AbstractWebAuthnAuditingAction.java
new file mode 100644
index 0000000..6756a22
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/AbstractWebAuthnAuditingAction.java
@@ -0,0 +1,205 @@
+/*
+ * 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.audit.impl;
+
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.EventContext;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.springframework.webflow.execution.Event;
+import org.springframework.webflow.execution.RequestContext;
+
+import net.shibboleth.idp.authn.context.AuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.audit.WebAuthnAuditFields;
+import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnAction;
+import net.shibboleth.idp.profile.audit.impl.PopulateAuditContext;
+import net.shibboleth.idp.profile.audit.impl.WriteAuditLog;
+import net.shibboleth.profile.context.AuditContext;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * A base class for actions that include audit logging support.
+ */
+public abstract class AbstractWebAuthnAuditingAction<T> extends AbstractWebAuthnAction<T> {
+    
+    /** Strategy used to locate or create the {@link AuditContext} to populate. */
+    @Nonnull private Function<ProfileRequestContext,AuditContext> auditContextCreationStrategy;
+
+    /** Optional audit extraction action. */
+    @Nullable private PopulateAuditContext populateAuditContextAction;
+
+    /** Optional audit output action. */
+    @Nullable private WriteAuditLog writeAuditLogAction;
+    
+    /** The Spring RequestContext to operate on. */
+    @Nullable private RequestContext requestContext;
+    
+    /** Constructor. */
+    protected AbstractWebAuthnAuditingAction(@Nonnull final Function<ProfileRequestContext,T> defaultStrategy) {
+        super(defaultStrategy);
+        auditContextCreationStrategy =
+                new ChildContextLookup<>(AuditContext.class, true).compose(
+                        new ChildContextLookup<>(AuthenticationContext.class));
+    }
+
+    /**
+     * Set the strategy used to locate the {@link AuditContext} associated with a given
+     * {@link ProfileRequestContext}.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setAuditContextCreationStrategy(@Nonnull final Function<ProfileRequestContext,AuditContext> strategy) {
+        checkSetterPreconditions();
+        auditContextCreationStrategy = Constraint.isNotNull(strategy, "AuditContext creation strategy cannot be null");
+    }
+    
+    /**
+     * Sets an audit context population action to run.
+     * 
+     * @param action optional action to use to populate audit context
+     */
+    public void setPopulateAuditContextAction(@Nullable final PopulateAuditContext action) {
+        checkSetterPreconditions();
+        populateAuditContextAction = action;
+    }
+
+    /**
+     * Sets an audit output action to run.
+     * 
+     * @param action optional action to use to write to audit log
+     */
+    public void setWriteAuditLogAction(@Nullable final WriteAuditLog action) {
+        checkSetterPreconditions();
+        writeAuditLogAction = action;
+    }
+    
+    /**
+     * Create or locate the {@link AuditContext} via the defined strategy.
+     * 
+     * @param profileRequestContext profile request context
+     * 
+     * @return the audit context
+     */
+    @Nullable protected AuditContext getAuditContext(@Nonnull final ProfileRequestContext profileRequestContext) {
+        return auditContextCreationStrategy.apply(profileRequestContext);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected Event doExecute(@Nonnull final RequestContext springRequestContext,
+            @Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        requestContext = springRequestContext;
+        return super.doExecute(springRequestContext, profileRequestContext);
+    }
+    
+    /**
+     * Audit a successful operation. This will automatically add a 'success' field to the audit context.
+     * 
+     * @param profileRequestContext the profile request context
+     * @param action a descriptive string of the action that was performed. Can be {@code null} if not used.
+     */
+    protected void auditSuccess(@Nonnull final ProfileRequestContext profileRequestContext, 
+            @Nullable final String action) {
+        doAudit(profileRequestContext, true, action);
+    }
+
+    /**
+     * Audit a failed operation. This will automatically add a 'failure' field to the audit context.
+     * 
+     * @param profileRequestContext the profile request context
+     * @param action a descriptive string of the action that was performed. Can be {@code null} if not used.
+     */
+    protected void auditFailure(@Nonnull final ProfileRequestContext profileRequestContext, 
+            @Nullable final String action) {
+        doAudit(profileRequestContext, false, action);
+    }
+    
+// Checkstyle: CyclomaticComplexity OFF
+    /**
+     * Do audit extraction and output.
+     * 
+     * @param profileRequestContext profile request context
+     * @param success true iff this is an audit of successful action, false otherwise
+     * @param action the type of action that was performed
+     */
+    protected void doAudit(@Nonnull final ProfileRequestContext profileRequestContext, final boolean success, 
+            @Nullable final String action) {
+
+        if (populateAuditContextAction != null && writeAuditLogAction != null) {
+            final EventContext existingEvent = profileRequestContext.getSubcontext(EventContext.class);
+            
+            try {
+                assert populateAuditContextAction != null;
+                populateAuditContextAction.execute(requestContext);
+
+                final AuditContext ac = getAuditContext(profileRequestContext);
+                if (ac != null) {
+                    final Map<String,String> fields = getAuditFields(profileRequestContext);
+                    if (fields != null) {
+                        for (final Map.Entry<String,String> field : fields.entrySet()) {
+                            final String key = field.getKey();
+                            assert key != null;
+                            ac.getFieldValues(key).add(field.getValue());
+                        }
+                    }
+                    
+                    // Add a success or failure field
+                    if (success) {
+                        ac.getFields().put(WebAuthnAuditFields.ACTION_OUTCOME, "success");
+                    } else {
+                        // TODO this could come from an error context
+                        ac.getFields().put(WebAuthnAuditFields.ACTION_OUTCOME, "failure");
+                    }
+                    if (action != null && !action.isEmpty()) {
+                        ac.getFields().put(WebAuthnAuditFields.ACTION, action);
+                    }
+                }
+            } finally {
+                if (existingEvent != null) {
+                    profileRequestContext.addSubcontext(existingEvent);
+                }
+            }
+            
+            try {
+                assert writeAuditLogAction != null;
+                writeAuditLogAction.execute(requestContext);
+            } finally {
+                if (existingEvent != null) {
+                    profileRequestContext.addSubcontext(existingEvent);
+                }
+            }
+        }
+    }
+// Checkstyle: CyclomaticComplexity ON
+    
+    /**
+     * Subclasses can override this method to supply additional audit fields to store.
+     * 
+     * @param profileRequestContext profile request context
+     * @return audit fields
+     */
+    @Nullable @Unmodifiable @NotLive protected Map<String,String> getAuditFields(
+            @Nonnull final ProfileRequestContext profileRequestContext) {
+        return null;
+    }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnCredentialToRemoveAuditExtractor.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnCredentialToRemoveAuditExtractor.java
new file mode 100644
index 0000000..a685af3
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnCredentialToRemoveAuditExtractor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.audit.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.audit.AbstractWebAuthnManagementAuditExtractor;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
+
+/**
+ * {@link Function} that returns the base64 encoded credential identifier of the credential that has been removed.
+ */
+public class WebAuthnCredentialToRemoveAuditExtractor  extends AbstractWebAuthnManagementAuditExtractor<String> {
+
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the {@link WebAuthnManagementContext}.
+     */
+    public WebAuthnCredentialToRemoveAuditExtractor(
+            @Nonnull final Function<ProfileRequestContext, WebAuthnManagementContext> strategy) {
+        super(strategy);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected String doLookup(@Nonnull final WebAuthnManagementContext context) {
+        final byte[] credentialToRemove = context.getCredentialIdToRemove();
+        if (credentialToRemove == null) {
+            return null;
+        }
+
+        try {
+            return Base64Support.encodeURLSafe(credentialToRemove);
+        } catch (final EncodingException e) {
+            return null;
+        }
+
+    }
+ 
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnSearchUserAuditExtractor.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnSearchUserAuditExtractor.java
new file mode 100644
index 0000000..f2a7321
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnSearchUserAuditExtractor.java
@@ -0,0 +1,47 @@
+/*
+ * 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.audit.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.audit.AbstractWebAuthnManagementAuditExtractor;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnManagementContext;
+
+/**
+ * {@link Function} that returns which username that the admin user searched for.
+ */
+public class WebAuthnSearchUserAuditExtractor extends AbstractWebAuthnManagementAuditExtractor<String> {
+
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the {@link WebAuthnManagementContext}.
+     */
+    public WebAuthnSearchUserAuditExtractor(
+            @Nonnull final Function<ProfileRequestContext, WebAuthnManagementContext> strategy) {
+        super(strategy);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected String doLookup(@Nonnull final WebAuthnManagementContext context) {
+        return context.getSearchUsername();
+    }
+ 
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnUserCredentialAddedAuditExtractor.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnUserCredentialAddedAuditExtractor.java
new file mode 100644
index 0000000..c612c4e
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnUserCredentialAddedAuditExtractor.java
@@ -0,0 +1,63 @@
+/*
+ * 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.audit.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.audit.AbstractWebAuthnRegistrationAuditExtractor;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
+
+/**
+ * {@link Function} that returns the base64 encoded credential identifier of the credential that has been removed.
+ */
+public class WebAuthnUserCredentialAddedAuditExtractor  extends AbstractWebAuthnRegistrationAuditExtractor<String> {
+
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the {@link WebAuthnRegistrationContext}.
+     */
+    public WebAuthnUserCredentialAddedAuditExtractor(
+            @Nonnull final Function<ProfileRequestContext, WebAuthnRegistrationContext> strategy) {
+        super(strategy);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected String doLookup(@Nonnull final WebAuthnRegistrationContext context) {
+        final var result = context.getRegistrationResult();
+        if (result == null) {
+            return null;
+        }
+        final byte[] credentialToRemove = result.getKeyId().getId().getBytes();
+        if (credentialToRemove == null) {
+            return null;
+        }
+
+        try {
+            return Base64Support.encodeURLSafe(credentialToRemove);
+        } catch (final EncodingException e) {
+            return null;
+        }
+
+    }
+ 
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnUserCredentialToRemoveAuditExtractor.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnUserCredentialToRemoveAuditExtractor.java
new file mode 100644
index 0000000..ba9e954
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/audit/impl/WebAuthnUserCredentialToRemoveAuditExtractor.java
@@ -0,0 +1,59 @@
+/*
+ * 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.audit.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.authn.webauthn.audit.AbstractWebAuthnRegistrationAuditExtractor;
+import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.shared.codec.Base64Support;
+import net.shibboleth.shared.codec.EncodingException;
+
+/**
+ * {@link Function} that returns the base64 encoded credential identifier of the credential that has been removed.
+ */
+public class WebAuthnUserCredentialToRemoveAuditExtractor  extends AbstractWebAuthnRegistrationAuditExtractor<String> {
+
+    /**
+     * Constructor.
+     *
+     * @param strategy the strategy used to locate the {@link WebAuthnRegistrationContext}.
+     */
+    public WebAuthnUserCredentialToRemoveAuditExtractor(
+            @Nonnull final Function<ProfileRequestContext, WebAuthnRegistrationContext> strategy) {
+        super(strategy);
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected String doLookup(@Nonnull final WebAuthnRegistrationContext context) {
+        final byte[] credentialToRemove = context.getCredentialIdToRemove();
+        if (credentialToRemove == null) {
+            return null;
+        }
+
+        try {
+            return Base64Support.encodeURLSafe(credentialToRemove);
+        } catch (final EncodingException e) {
+            return null;
+        }
+
+    }
+ 
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAuthenticationAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
similarity index 57%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAuthenticationAction.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
index 269d6e6..95d73d2 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAuthenticationAction.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnAction.java
@@ -1,10 +1,7 @@
 /*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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
+ * 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
  *
@@ -22,17 +19,18 @@ import java.util.function.Function;
 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.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.authn.AbstractAuthenticationAction;
+import com.yubico.fido.metadata.FidoMetadataService;
+
 import net.shibboleth.idp.authn.AuthnEventIds;
-import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -41,37 +39,54 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * <p>A base class for WebAuthn authentication related actions.</p>
+ * <p>A base class for WebAuthn actions. Makes available the WebAuthn client, credential repository, and 
+ * metadata service</p>
+ * 
+ * <p>In addition to the work performed by {@link AbstractProfileAction}, this action also looks up
+ * and makes available a WebAuthn context <T>.</p>
  * 
- * <p>In addition to the work performed by {@link AbstractAuthenticationAction}, this action also looks up
- * and makes available the {@link WebAuthnAuthenticationContext}.</p>
+ * <p>WebAuthn implementations should override the {@link #doExecute(ProfileRequestContext, T)} method.</p>
  * 
- * <p>Webauthn authentication action implementations should override the
- * {@link #doExecute(ProfileRequestContext, AuthenticationContext, WebAuthnAuthenticationContext)} 
- * method.</p>
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @pre <pre>ProfileRequestContext.getSubcontext(ProfileRequestContext.class) != null</pre>
  * 
- * @event {@link AuthnEventIds#INVALID_AUTHN_CTX}
- * @pre <pre>ProfileRequestContext.getSubcontext(AuthenticationContext.class) != null</pre>
- * @post <pre>AuthenticationContext.getSubcontext(WebAuthnAuthenticationContext.class) != null</pre>
+ * @param <T> the WebAuthn context to lookup
  */
-//TODO somehow this should be a child of the base webauthn action (BaseWebAuthnAction)?
-public abstract class AbstractWebAuthnAuthenticationAction extends AbstractAuthenticationAction {
+public class AbstractWebAuthnAction<T> extends AbstractProfileAction {
     
     /** Class logger. */
-    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(AbstractWebAuthnAuthenticationAction.class);
+    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(AbstractWebAuthnAction.class);
     
-    /** Lookup strategy to locate the WebAuthn authentication context. */
-    @Nonnull private Function<ProfileRequestContext,WebAuthnAuthenticationContext> webauthnContextLookupStrategy;
+    /** Lookup strategy to locate the WebAuthn context. */
+    @Nonnull private Function<ProfileRequestContext,T> webauthnContextLookupStrategy;
     
-    /** The WebAuthn authentication Context.*/
-    @NonnullBeforeExec private WebAuthnAuthenticationContext webauthnContext;
+    /** The WebAuthn Context.*/
+    @NonnullBeforeExec private T webauthnContext;
     
     /** The WebAuthn client to use.*/
     @NonnullAfterInit private WebAuthnAuthenticationClient webAuthnClient;
     
-    /** The credential repository to store WebAuthn credentials in.*/
+    /** The credential repository to store valid credentials in.*/
     @Nullable private StorageServiceCredentialRepository credentialRepository;
     
+    /** Optional FIDO metadata service resolver.*/ 
+    @Nullable private FidoMetadataService fidoMetadataService;  
+    
+    /** Constructor.*/
+    protected AbstractWebAuthnAction(@Nonnull final Function<ProfileRequestContext,T> defaultStrategy) {
+        webauthnContextLookupStrategy = 
+                Constraint.isNotNull(defaultStrategy, "Default WebAuthn lookup strategy can not be null");
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        
+        if (webAuthnClient == null) {
+            throw new ComponentInitializationException("WebAuthn Client can not be null");
+        }
+    } 
     
     /**
      * Set the WebAuthn client used to handle registration and authentication ceremonies.
@@ -88,11 +103,31 @@ public abstract class AbstractWebAuthnAuthenticationAction extends AbstractAuthe
      * 
      * @return the WebAuthn client.
      */
-    @NonnullBeforeExec protected WebAuthnAuthenticationClient getWebAuthnClient() {
+    @NonnullAfterInit protected WebAuthnAuthenticationClient getWebAuthnClient() {
         checkComponentActive();
         return webAuthnClient;
     }
     
+    /**
+     * Set the FIDO Alliance metadata service resolver to use as the attestation trust source.
+     * 
+     * @param service The FIDO metadata service to set.
+     */
+    public void setFidoMetadataService(@Nullable final FidoMetadataService service) {
+        checkSetterPreconditions();
+        fidoMetadataService = service;
+    }
+    
+    /**
+     * Get the FIDO Alliance metadata service resolver to use as the attestation trust source.
+     * 
+     * @return the FIDO metadata service.
+     */
+    @Nullable protected FidoMetadataService getFidoMetadataService() {
+        checkComponentActive();
+        return fidoMetadataService;
+    }
+    
     /**
      * Set the credential repository used to store WebAuthn credentials.
      *  
@@ -110,23 +145,15 @@ public abstract class AbstractWebAuthnAuthenticationAction extends AbstractAuthe
      */
     @Nullable protected StorageServiceCredentialRepository getCredentialRepository() {
         return credentialRepository;
-    } 
-    
-    /** Constructor.*/
-    protected AbstractWebAuthnAuthenticationAction() {
-        //prc -> ac -> dc
-        webauthnContextLookupStrategy = new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
-                compose(new ChildContextLookup<>(AuthenticationContext.class));
     }
     
-    
     /**
-     * Set the WebAuthn authentication context lookup strategy to use.
+     * Set the WebAuthn context lookup strategy to use.
      * 
      * @param strategy lookup strategy
      */
-    public void setWebauthnContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,WebAuthnAuthenticationContext> strategy) {
+    public void setWebAuthnContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,T> strategy) {
         checkSetterPreconditions();
 
         webauthnContextLookupStrategy = 
@@ -135,61 +162,46 @@ public abstract class AbstractWebAuthnAuthenticationAction extends AbstractAuthe
     
     /** {@inheritDoc} */
     @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (webAuthnClient == null) {
-            throw new ComponentInitializationException("WebAuthn Client can not be null");
-        }
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected final boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
+    protected final boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        if (!super.doPreExecute(profileRequestContext, authenticationContext)) {
+        if (!super.doPreExecute(profileRequestContext)) {
             return false;
         }
       
         webauthnContext = webauthnContextLookupStrategy.apply(profileRequestContext);
         if (webauthnContext == null) {
-            log.warn("{} No WebAuthn authentication context returned by lookup strategy",getLogPrefix());
+            log.warn("{} No WebAuthn context returned by lookup strategy",getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
             return false;
             
         }
         assert webauthnContext != null;
-        return doPreExecute(profileRequestContext, authenticationContext, webauthnContext);
+        return doPreExecute(profileRequestContext, webauthnContext);
     }
     
     /**
-     * Delegates to {@link #doExecute(ProfileRequestContext, AuthenticationContext, 
-     * WebAuthnAuthenticationContext)} to perform the actual authentication. Implementations can not 
+     * Delegates to {@link #doExecute(ProfileRequestContext, WebAuthnAuthenticationContext)}. Implementations can not 
      * override this method.
      * 
      * @param profileRequestContext the current IdP profile request context
      * @param authenticationContext the current authentication context
      */
     @Override
-    protected final void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext) {
+    protected final void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         assert webauthnContext != null;
-        doExecute(profileRequestContext,authenticationContext, webauthnContext);
+        doExecute(profileRequestContext, webauthnContext);
     }
     
     /**
      * Performs this authentication action's pre-execute step. Default implementation just returns true.
      * 
      * @param profileRequestContext the current IdP profile request context
-     * @param authenticationContext the current authentication context
-     * @param context the WebAuthn authentication context
+     * @param context the WebAuthn context
      * 
      * @return true iff execution should continue
      */
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext,
-            @Nonnull final WebAuthnAuthenticationContext context) {
+            @Nonnull final T context) {
         return true;
     }
     
@@ -198,13 +210,12 @@ public abstract class AbstractWebAuthnAuthenticationAction extends AbstractAuthe
      * should override this method.
      * 
      * @param profileRequestContext the current IdP profile request context
-     * @param authenticationContext the current authentication context
-     * @param context the WebAuthn authentication context
+     * @param context the WebAuthn context
      */
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext,
-            @Nonnull final WebAuthnAuthenticationContext context) {
-        
+            @Nonnull final T context) {
+        // Default does nothing
     }
+    
 
 }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnBaseAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnBaseAction.java
deleted file mode 100644
index 34d30a7..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AbstractWebAuthnBaseAction.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You under the Apache
- * License, Version 2.0 (the "License"); you may not use this file except in
- * compliance with the License.  You may obtain a copy of the License at
- *
- *    http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.shibboleth.idp.plugin.authn.webauthn.impl;
-
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
-import org.opensaml.profile.action.ActionSupport;
-import org.opensaml.profile.action.EventIds;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.authn.context.AuthenticationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
-import net.shibboleth.shared.annotation.constraint.NotEmpty;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * <p>A base class for WebAuthn actions that require access to the base WebAuthn context.</p>
- * 
- * <p>In addition to the work performed by {@link AbstractProfileAction}, this action also looks up
- * and makes available the {@link BaseWebAuthnContext}.</p>
- * 
- * <p>WebAuthn action implementations should override the
- * {@link #doExecute(ProfileRequestContext, BaseWebAuthnContext)} method.</p>
- * 
- * @event {@link EventIds#INVALID_PROFILE_CTX}
- * @pre <pre>ProfileRequestContext.getSubcontext(ProfileRequestContext.class) != null</pre>
- * @post <pre>AuthenticationContext.getSubcontext(BaseWebAuthnContext.class) != null</pre>
- */
-//TODO a few abstract action types, remove overlap
-public abstract class AbstractWebAuthnBaseAction extends BaseWebAuthnAction {
-    
-    /** Class logger. */
-    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(AbstractWebAuthnBaseAction.class);
-    
-    /** Lookup strategy to locate the WebAuthn base context. */
-    @Nonnull 
-    private Function<ProfileRequestContext,BaseWebAuthnContext> webAuthnBaseContextLookupStrategy;
-    
-    /** The WebAuthn base Context.*/
-    @NonnullBeforeExec private BaseWebAuthnContext webAuthnBaseContext;
-            
-    
-    /** Constructor.*/
-    protected AbstractWebAuthnBaseAction() {
-        webAuthnBaseContextLookupStrategy = new ChildContextLookup<>(BaseWebAuthnContext.class).
-                compose(new ChildContextLookup<>(AuthenticationContext.class));
-    }
-    
-    
-    /**
-     * Set WebAuthn base context lookup strategy to use.
-     * 
-     * @param strategy lookup strategy
-     */
-    public void setWebAuthnBaseContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,BaseWebAuthnContext> strategy) {
-        checkSetterPreconditions();
-
-        webAuthnBaseContextLookupStrategy = 
-                Constraint.isNotNull(strategy, "WebauthnContextLookuplookup strategy cannot be null");
-    }    
-
-    
-    /** {@inheritDoc} */
-    @Override
-    protected final boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-      
-        webAuthnBaseContext = webAuthnBaseContextLookupStrategy.apply(profileRequestContext);
-        if (webAuthnBaseContext == null) {
-            log.warn("{} No WebAuthn base context returned by lookup strategy",getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-            
-        }
-        assert webAuthnBaseContext != null;
-        return doPreExecute(profileRequestContext, webAuthnBaseContext);
-    }
-    
-    /**
-     * Delegates to {@link #doExecute(ProfileRequestContext, BaseWebAuthnContext)} to perform the 
-     * actual action. Implementations can not override this method.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     */
-    @Override
-    protected final void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
-        assert webAuthnBaseContext != null;
-        doExecute(profileRequestContext, webAuthnBaseContext);
-    }
-    
-    /**
-     * Performs this action's pre-execute step. Default implementation just returns true.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     * @param context the WebAuthn base context
-     * 
-     * @return true iff execution should continue
-     */
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final BaseWebAuthnContext context) {
-        return true;
-    }
-    
-    /**
-     * Performs this WebAuthn authentication action using the supplied WebAuthn context. Implementations
-     * should override this method.
-     * 
-     * @param profileRequestContext the current IdP profile request context
-     * @param context the WebAuthn base context
-     */
-    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final BaseWebAuthnContext context) {
-        
-    }
-
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AddUserVerificationRequirement.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AddUserVerificationRequirement.java
index 2e485e9..731d212 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AddUserVerificationRequirement.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/AddUserVerificationRequirement.java
@@ -18,11 +18,13 @@ import java.util.stream.Stream;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.yubico.webauthn.data.UserVerificationRequirement;
 
+import net.shibboleth.idp.authn.context.AuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
@@ -36,7 +38,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(BaseWebAuthnContext.class) != null</pre>
  * @post <pre>WebAuthnRegistrationContext.getUserVerificationRequirement() != null</pre>
  */
-public class AddUserVerificationRequirement extends AbstractWebAuthnBaseAction {
+public class AddUserVerificationRequirement extends AbstractWebAuthnAction<BaseWebAuthnContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AddUserVerificationRequirement.class);
@@ -46,6 +48,8 @@ public class AddUserVerificationRequirement extends AbstractWebAuthnBaseAction {
     
     /** Constructor.*/
     public AddUserVerificationRequirement() {
+        super(new ChildContextLookup<>(BaseWebAuthnContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
         userVerificationRequirement = UserVerificationRequirement.PREFERRED;
     }
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/BaseWebAuthnAction.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/BaseWebAuthnAction.java
deleted file mode 100644
index 4ff3e87..0000000
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/BaseWebAuthnAction.java
+++ /dev/null
@@ -1,115 +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.impl;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import com.yubico.fido.metadata.FidoMetadataService;
-
-import net.shibboleth.idp.plugin.authn.webauthn.client.WebAuthnAuthenticationClient;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
-import net.shibboleth.idp.profile.AbstractProfileAction;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-
-/**
- * A base action for WebAuthn actions that require access to the client, credential repository, and metadata service.
- */
-public abstract class BaseWebAuthnAction extends AbstractProfileAction {
-    
-    /** The WebAuthn client to use.*/
-    @NonnullAfterInit private WebAuthnAuthenticationClient webAuthnClient;
-    
-    /** The credential repository to store valid credentials in.*/
-    @NonnullAfterInit private StorageServiceCredentialRepository credentialRepository;
-    
-    /** Optional FIDO metadata service resolver.*/ 
-    @Nullable private FidoMetadataService fidoMetadataService;  
-    
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-        
-        if (webAuthnClient == null) {
-            throw new ComponentInitializationException("WebAuthn Client can not be null");
-        }
-        if (credentialRepository == null) {
-            throw new ComponentInitializationException("CredentialRepository can not be null");
-        }
-    }    
-    
-    /**
-     * Set the WebAuthn client used to handle registration and authentication ceremonies.
-     * 
-     * @param client The WebAuthn client to set.
-     */
-    public void setWebAuthnClient(@Nonnull final WebAuthnAuthenticationClient client) {
-        checkSetterPreconditions();
-        webAuthnClient = Constraint.isNotNull(client, "WebAuthn client can not be null");
-    }
-    
-    /**
-     * Get the WebAuthn client used to handle registration and authentication ceremonies.
-     * 
-     * @return the webAuthnClient.
-     */
-    @NonnullAfterInit protected WebAuthnAuthenticationClient getWebAuthnClient() {
-        checkComponentActive();
-        return webAuthnClient;
-    }
-    
-    /**
-     * Set the FIDO Alliance metadata service resolver to use as the attestation trust source.
-     * 
-     * @param service The FIDO metadata service to set.
-     */
-    public void setFidoMetadataService(@Nullable final FidoMetadataService service) {
-        checkSetterPreconditions();
-        fidoMetadataService = service;
-    }
-    
-    /**
-     * Get the FIDO Alliance metadata service resolver to use as the attestation trust source.
-     * 
-     * @return the FIDO metadata service.
-     */
-    @Nullable protected FidoMetadataService getFidoMetadataService() {
-        checkComponentActive();
-        return fidoMetadataService;
-    }
-    
-    /**
-     * Set the credential repository used to store WebAuthn credentials.
-     *  
-     * @param repository The respository to set.
-     */
-    public void setCredentialRepository(@Nonnull final StorageServiceCredentialRepository repository) {
-        checkSetterPreconditions();
-        credentialRepository = Constraint.isNotNull(repository, "Credential respository can not be null");
-    }
-    
-    /**
-     * Get the credential repository used to store WebAuthn credentials.
-     * 
-     * @return the credential repository.
-     */
-    @NonnullAfterInit protected StorageServiceCredentialRepository getCredentialRepository() {
-        return credentialRepository;
-    }
-
-}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
index 1fc493c..4b9838f 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/CreatePublicKeyCredentialRequestOptions.java
@@ -22,6 +22,7 @@ import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -47,14 +48,19 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnAuthenticationContext.class) != null</pre>
  * @post A PublicKeyCredentialRequestOptions is added to the authentication context
  */
-public class CreatePublicKeyCredentialRequestOptions extends AbstractWebAuthnAuthenticationAction {
+public class CreatePublicKeyCredentialRequestOptions extends AbstractWebAuthnAction<WebAuthnAuthenticationContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(CreatePublicKeyCredentialRequestOptions.class);
     
+    /** Constructor.  */
+    protected CreatePublicKeyCredentialRequestOptions() {
+        super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
+    }
+    
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext, 
             @Nonnull final WebAuthnAuthenticationContext context) {
         
         final WebAuthnAuthenticationClient client = getWebAuthnClient();  
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/EnsureAllowedCredentialsIsEmpty.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/EnsureAllowedCredentialsIsEmpty.java
index 5425528..fd0656a 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/EnsureAllowedCredentialsIsEmpty.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/EnsureAllowedCredentialsIsEmpty.java
@@ -16,6 +16,7 @@ package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
@@ -29,15 +30,20 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnAuthenticationContext.class) != null</pre>
  * @post <pre>WebAuthnAuthenticationContext.getExistingCredentials() == null</pre>
  */
-public class EnsureAllowedCredentialsIsEmpty extends AbstractWebAuthnAuthenticationAction {
+public class EnsureAllowedCredentialsIsEmpty extends AbstractWebAuthnAction<WebAuthnAuthenticationContext> {    
 
     /** Class logger. */
     @Nonnull
     private final Logger log = LoggerFactory.getLogger(EnsureAllowedCredentialsIsEmpty.class);
+    
+    /** Constructor. */
+    protected EnsureAllowedCredentialsIsEmpty() {
+        super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
+    }
 
     /** {@inheritDoc} */
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext,
             @Nonnull final WebAuthnAuthenticationContext context) {        
         
         log.trace("{} Removing any existing credentials found from the authentication request", getLogPrefix());
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialAssertionFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialAssertionFromFormRequest.java
index 6333e31..1e5be8d 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialAssertionFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractPublicKeyCredentialAssertionFromFormRequest.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
@@ -50,7 +51,8 @@ import net.shibboleth.shared.primitive.StringSupport;
  * @pre <pre>ProfileRequestContext.getSubcontext(WebAuthnAuthenticationContext.class) != null</pre>
  * @post <pre>WebAuthnAuthenticationContext.getAuthenticatorAssertionResponse() == null</pre>
  */
-public class ExtractPublicKeyCredentialAssertionFromFormRequest extends AbstractWebAuthnAuthenticationAction {
+public class ExtractPublicKeyCredentialAssertionFromFormRequest 
+                    extends AbstractWebAuthnAction<WebAuthnAuthenticationContext> {
 
     /** Default public key credential assertion parameter name. */
     @Nonnull @NotEmpty public static final String DEFAULT_PARAMETER_NAME = "publicKeyCredential";
@@ -65,7 +67,9 @@ public class ExtractPublicKeyCredentialAssertionFromFormRequest extends Abstract
     @NonnullAfterInit private ObjectMapper objectMapper;
     
     /** Constructor. */
-    public ExtractPublicKeyCredentialAssertionFromFormRequest() {       
+    public ExtractPublicKeyCredentialAssertionFromFormRequest() {
+        super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
         publicKeyCredentialAssertionParameterName = DEFAULT_PARAMETER_NAME;
     }
     
@@ -102,7 +106,6 @@ public class ExtractPublicKeyCredentialAssertionFromFormRequest extends Abstract
     
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext,
             @Nonnull final WebAuthnAuthenticationContext context) {
         
         final HttpServletRequest request = getHttpServletRequest();
@@ -114,7 +117,7 @@ public class ExtractPublicKeyCredentialAssertionFromFormRequest extends Abstract
 
         final String pkCredAssertionJson = request.getParameter(publicKeyCredentialAssertionParameterName);       
         if (pkCredAssertionJson == null) {
-            log.debug("{} No PublicKeyCredential with authenticator attestation response in request",getLogPrefix());
+            log.debug("{} No PublicKeyCredential with authenticator assertion response in request",getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return;
         }   
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/GenerateServerChallenge.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/GenerateServerChallenge.java
index 2c05b29..156ee37 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/GenerateServerChallenge.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/GenerateServerChallenge.java
@@ -24,11 +24,13 @@ import java.util.function.Function;
 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.action.EventIds;
 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.BaseWebAuthnContext;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -43,7 +45,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @event {@link org.opensaml.profile.action.EventIds#INVALID_PROFILE_CTX}
  * @post a challenge is added to the {@link BaseWebAuthnContext base context}
  */
-public class GenerateServerChallenge extends AbstractWebAuthnBaseAction {
+public class GenerateServerChallenge extends AbstractWebAuthnAction<BaseWebAuthnContext> {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(GenerateServerChallenge.class);
@@ -53,6 +55,8 @@ public class GenerateServerChallenge extends AbstractWebAuthnBaseAction {
     
     /** Constructor. */
     public GenerateServerChallenge() {
+        super(new ChildContextLookup<>(BaseWebAuthnContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
         challengeGeneratorStrategy = new DefaultChallengeGenerator();
     }
     
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 abcadef..16ba7d5 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
@@ -20,6 +20,7 @@ import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -28,10 +29,14 @@ import org.slf4j.Logger;
 import com.yubico.webauthn.data.ByteArray;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
+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.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.PredicateSupport;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -46,7 +51,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * the credentials from the credential repository. {@link BaseWebAuthnContext#getUserId()} != null if the userHandle is
  * found from the username. 
  */
-public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
+public class LookupRegisteredCredentials extends AbstractWebAuthnAction<BaseWebAuthnContext> {
 
     /** Class logger. */
     @Nonnull
@@ -61,8 +66,23 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
      */
     @Nonnull @NotEmpty private String noCredentialsEventId;
     
+    /** The credential repository to use.*/
+    @NonnullAfterInit private StorageServiceCredentialRepository repository;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }
+    
     /** Constructor.  */
     public LookupRegisteredCredentials() {
+        super(new ChildContextLookup<>(BaseWebAuthnContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
         noCredentialsEventId = WebAuthnAuthenticationEventIds.NO_REGISTERED_WEBAUTHN_CREDENTIALS;
         triggerEventOnNoCredentialsPredicate = PredicateSupport.alwaysFalse();
     }
@@ -111,12 +131,12 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
         } 
         
         final Collection<CredentialRegistration> credentials =
-                getCredentialRepository().getRegistrationsByUsername(username);   
+                repository.getRegistrationsByUsername(username);   
 
         log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(), credentials.size(), username);
         context.setExistingCredentials(credentials);
         
-        final Optional<ByteArray> userHandle = getCredentialRepository().getUserHandleForUsername(username);
+        final Optional<ByteArray> userHandle = repository.getUserHandleForUsername(username);
         if (userHandle.isPresent()) {
             final byte[] userId = userHandle.get().getBytes();
             assert userId != null;
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 b0fe054..d739e5e 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
@@ -20,6 +20,7 @@ import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
@@ -36,6 +37,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.authn.WebAuthnAuthenticationEven
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
@@ -53,7 +55,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @post BaseWebAuthnContext.setExistingCredentials() is either null if no existing credentials are found, or contains
  * the credentials from the credential repository
  */
-public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnAuthenticationAction {
+public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnAction<WebAuthnAuthenticationContext> {
 
     /** Class logger. */
     @Nonnull
@@ -68,8 +70,23 @@ public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnA
      */
     @Nonnull @NotEmpty private String noCredentialsEventId;
     
+    /** The credential repository to use.*/
+    @NonnullAfterInit private StorageServiceCredentialRepository repository;
+    
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+        repository = getCredentialRepository();
+        if (repository == null) {
+            throw new ComponentInitializationException("Credential repository can not be null");
+        }
+    }
+    
     /** Constructor.  */
     public LookupRegisteredCredentialsFromUserHandle() {
+        super(new ChildContextLookup<>(WebAuthnAuthenticationContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class)));
         noCredentialsEventId = WebAuthnAuthenticationEventIds.NO_REGISTERED_WEBAUTHN_CREDENTIALS;
         triggerEventOnNoCredentialsPredicate = PredicateSupport.alwaysFalse();
     }
@@ -105,20 +122,9 @@ public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnA
         checkSetterPreconditions();
         noCredentialsEventId = Constraint.isNotEmpty(eventId, "NoCredentialsEventId can not be null or empty");
     }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected void doInitialize() throws ComponentInitializationException {
-        super.doInitialize();
-
-        if (getCredentialRepository() ==  null) {
-            throw new ComponentInitializationException("The credential repository can not be null");
-        }
-    }
 
     /** {@inheritDoc} */
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final AuthenticationContext authenticationContext,
             @Nonnull final WebAuthnAuthenticationContext context) {
         
         final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion = 
@@ -130,9 +136,6 @@ public class LookupRegisteredCredentialsFromUserHandle extends AbstractWebAuthnA
         } 
         final String username = context.getUsername();
         
-        final StorageServiceCredentialRepository repository = getCredentialRepository();
-        assert repository != null;
-        
         final Optional<ByteArray> userHandle = assertion.getResponse().getUserHandle();
         boolean credentialsFound = false;
         if (userHandle.isEmpty()) {
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 42c252d..b8c70ba 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
@@ -58,7 +58,7 @@
     
     <bean id="LookupCredentialsForUser" parent="AbstractWebAuthnBaseAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LookupCredentialsForUser"
-        p:webauthnManagementContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext" />
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnManagementContext" />
         
     <bean id="ExtractKeyRemovalInformationFromFormRequest" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ExtractKeyRemovalInformationFromFormRequest"
@@ -69,7 +69,11 @@
     </bean>
         
     <bean id="DeletePublicKeyCredential" parent="AbstractWebAuthnManagementAction" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AdminDeletePublicKeyCredential" />
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AdminDeletePublicKeyCredential" 
+        p:populateAuditContextAction="#{%{idp.authn.webauthn.admin.management.audit.enabled:false} ? getObject('PostAdminFunctionPopulateAuditContext') : null}"
+        p:writeAuditLogAction="#{%{idp.authn.webauthn.admin.management.audit.enabled:false} ? getObject('WriteAdminAuditLog') : null}"
+        p:auditContextCreationStrategy-ref="AdminAuditContextLookup" />
+        
 
     <!-- Default functions to produce messages for the management view. -->
     <bean id="DefaultAdminInfoMessageFunction" class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.RegistrationInfoMessageLookupFunction" lazy-init="true"
@@ -81,6 +85,40 @@
     <alias alias="AdminInfoMessageFunction" name="%{idp.authn.webauthn.admin.management.infoMessageFunction:DefaultAdminInfoMessageFunction}" />       
     <alias alias="AdminErrorMessageFunction" name="%{idp.authn.webauthn.admin.management.errorMessageFunction:DefaultAdminErrorMessageFunction}" />
     
+    <!--  Audit logging beans -->    
+    
+    <util:map id="shibboleth.admin.AuditFormattingMap">
+        <entry key="#{'%{idp.authn.webauthn.admin.management.audit.category:Shibboleth-Audit.WebAuthnManagment}'.trim()}"
+            value="#{'%{idp.authn.webauthn.admin.management.audit.format:%a|%T|%u|%WebAuthnAdminAO|%WebAuthnAdminAction|%WebAuthnAdminCR|%WebAuthnAdminAU|%UA}'.trim()}" />
+    </util:map>
+    
+    <bean id="PostAdminFunctionPopulateAuditContext" parent="shibboleth.admin.AbstractPopulateAuditContext"
+        p:fieldExtractors="#{getObject('shibboleth.authn.webauthn.admin.management.audit.AuditExtractors') ?: getObject('shibboleth.authn.webauthn.admin.management.DefaultAuditExtractors')}"
+        p:clearAuditContext="true"/>
+        
+     <bean id="shibboleth.authn.webauthn.admin.management.DefaultAuditExtractors" parent="shibboleth.admin.DefaultAuditExtractors" 
+        lazy-init="true" class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+             <map merge="true">  
+                 <entry>
+                    <key>
+                        <util:constant
+                            static-field="net.shibboleth.idp.plugin.authn.webauthn.audit.WebAuthnAuditFields.ADMIN_AFFECTED_USER" />
+                    </key>
+                    <bean class="net.shibboleth.idp.plugin.authn.webauthn.audit.impl.WebAuthnSearchUserAuditExtractor"
+                        c:_0-ref="shibboleth.ChildLookup.WebAuthnManagementContext" />
+                </entry>
+                <entry>
+                    <key>
+                        <util:constant
+                            static-field="net.shibboleth.idp.plugin.authn.webauthn.audit.WebAuthnAuditFields.CRED_REMOVED" />
+                    </key>
+                    <bean class="net.shibboleth.idp.plugin.authn.webauthn.audit.impl.WebAuthnCredentialToRemoveAuditExtractor"
+                        c:_0-ref="shibboleth.ChildLookup.WebAuthnManagementContext" />
+                </entry>
+             </map>
+        </property>
+    </bean>
 
 
 </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 3ec5428..17f1127 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
@@ -70,16 +70,16 @@
 
     <bean id="AddUserVerificationRequired" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.AddUserVerificationRequirement" scope="prototype"
-        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"
         p:userVerificationRequirement="%{idp.authn.webauthn.registration.userVerification:discouraged}" />
 
     <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnBaseAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
-        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
 
     <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
-        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
 
     <bean id="AddUserId" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.AddUserId"
@@ -119,14 +119,21 @@
      </bean>
 
     <bean id="DeletePublicKeyCredential" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
-        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.DeletePublicKeyCredential" />
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.DeletePublicKeyCredential" 
+        p:populateAuditContextAction="#{%{idp.authn.webauthn.registration.audit.enabled:false} ? getObject('RegistrationOperationPopulateAuditContext') : null}"
+        p:writeAuditLogAction="#{%{idp.authn.webauthn.registration.audit.enabled:false} ? getObject('WriteAdminAuditLog') : null}"
+        p:auditContextCreationStrategy-ref="AdminAuditContextLookup" />        
 
     <bean id="ValidateAuthenticatorAttestationResponse" parent="AbstractWebAuthnRegistrationAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.ValidateAuthenticatorAttestationResponse" />
 
     <bean id="StorePublicKeyCredential" parent="AbstractWebAuthnRegistrationAction" scope="prototype"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.StorePublicKeyCredential"
-        p:credentialRepository-ref="shibboleth.authn.webauthn.DefaultCredentialRepository" />
+        p:credentialRepository-ref="shibboleth.authn.webauthn.DefaultCredentialRepository"
+        p:populateAuditContextAction="#{%{idp.authn.webauthn.registration.audit.enabled:false} ? getObject('RegistrationOperationPopulateAuditContext') : null}"
+        p:writeAuditLogAction="#{%{idp.authn.webauthn.registration.audit.enabled:false} ? getObject('WriteAdminAuditLog') : null}"
+        p:auditContextCreationStrategy-ref="AdminAuditContextLookup" />        
+
     
     <!-- Default functions to produce messages for the registration view. -->
     <bean id="DefaultRegistrationInfoMessageFunction" class="net.shibboleth.idp.plugin.authn.webauthn.context.navigate.RegistrationInfoMessageLookupFunction" lazy-init="true"
@@ -137,5 +144,40 @@
     <alias alias="RegistrationInfoMessageFunction" name="%{idp.authn.webauthn.registration.infoMessageFunction:DefaultRegistrationInfoMessageFunction}" />       
     <alias alias="RegistrationErrorMessageFunction" name="%{idp.authn.webauthn.registration.errorMessageFunction:DefaultRegistrationErrorMessageFunction}" />
 
+     <!--  Audit logging beans -->    
+    
+    <util:map id="shibboleth.admin.AuditFormattingMap">
+        <entry key="#{'%{idp.authn.webauthn.registration.audit.category:Shibboleth-Audit.WebAuthnRegistration}'.trim()}"
+            value="#{'%{idp.authn.webauthn.registration.audit.format:%a|%T|%u|%WebAuthnAdminAO|%WebAuthnAdminAction|%WebAuthnAdminCR|%WebAuthnAdminCA|%WebAuthnAdminAU|%UA}'.trim()}" />
+    </util:map>
+    
+    
+    <bean id="RegistrationOperationPopulateAuditContext" parent="shibboleth.admin.AbstractPopulateAuditContext"
+        p:fieldExtractors="#{getObject('shibboleth.authn.webauthn.registration.audit.AuditExtractors') ?: getObject('shibboleth.authn.webauthn.registration.DefaultAuditExtractors')}"
+        p:clearAuditContext="true"/>
+        
+     <bean id="shibboleth.authn.webauthn.registration.DefaultAuditExtractors" parent="shibboleth.admin.DefaultAuditExtractors" 
+        lazy-init="true" class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+             <map merge="true">  
+                 <entry>
+                    <key>
+                        <util:constant
+                            static-field="net.shibboleth.idp.plugin.authn.webauthn.audit.WebAuthnAuditFields.CRED_REMOVED" />
+                    </key>
+                    <bean class="net.shibboleth.idp.plugin.authn.webauthn.audit.impl.WebAuthnUserCredentialToRemoveAuditExtractor"
+                        c:_0-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
+                </entry>
+                <entry>
+                    <key>
+                        <util:constant
+                            static-field="net.shibboleth.idp.plugin.authn.webauthn.audit.WebAuthnAuditFields.CRED_ADDED" />
+                    </key>
+                    <bean class="net.shibboleth.idp.plugin.authn.webauthn.audit.impl.WebAuthnUserCredentialAddedAuditExtractor"
+                        c:_0-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext" />
+                </entry>
+             </map>
+        </property>
+    </bean>
 
 </beans>
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 d500b4b..2980666 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
@@ -55,8 +55,51 @@
             </bean>
         </constructor-arg>
     </bean>
+    
+    <bean id="WriteAdminAuditLog"
+        class="net.shibboleth.idp.profile.audit.impl.WriteAuditLog" scope="prototype" lazy-init="true"
+        p:formattingMap-ref="shibboleth.admin.AuditFormattingMap"
+        p:dateTimeFormat="#{getObject('shibboleth.AuditDateTimeFormat')}"
+        p:useDefaultTimeZone="#{getObject('shibboleth.AuditDefaultTimeZone') ?: false}"
+        p:includeProfileLoggingId="false"
+        p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier" 
+        p:activationCondition="%{idp.authn.audit.enabled:false}"
+        p:auditContextLookupStrategy-ref="AdminAuditContextLookup"/>
+        
+     <!-- Private copy of AuditContext for admin flows. TODO this is the default, is there somewhere better to put it?-->    
+    <bean id="AdminAuditContextLookup"
+        class="org.opensaml.messaging.context.navigate.ChildContextLookup"
+        c:type="#{ T(net.shibboleth.profile.context.AuditContext) }"
+        c:createContext="true" />
+     
+     <bean id="shibboleth.admin.AuditFormattingMapParser" scope="prototype" lazy-init="true"
+        class="net.shibboleth.idp.profile.audit.impl.PopulateAuditContext.FormattingMapParser"
+        c:_0-ref="shibboleth.admin.AuditFormattingMap" />
        
-       
+     <bean id="shibboleth.admin.AbstractPopulateAuditContext" abstract="true"
+        class="net.shibboleth.idp.profile.audit.impl.PopulateAuditContext" scope="prototype"
+        p:auditContextCreationStrategy-ref="AdminAuditContextLookup"
+        p:formattingMapParser-ref="shibboleth.admin.AuditFormattingMapParser"
+        p:dateTimeFormat="#{getObject('shibboleth.AuditDateTimeFormat')}"
+        p:useDefaultTimeZone="#{getObject('shibboleth.AuditDefaultTimeZone') ?: false}"
+        p:fieldReplacements="#{getObject('shibboleth.AuditFieldReplacementMap')}" />
+        
+          
+    <bean id="shibboleth.admin.DefaultAuditExtractors" lazy-init="true"
+            class="org.springframework.beans.factory.config.MapFactoryBean">
+        <property name="sourceMap">
+             <map>  
+                <entry>
+                    <key>
+                        <util:constant static-field="net.shibboleth.idp.profile.IdPAuditFields.USERNAME"/>
+                    </key>
+                    <bean parent="shibboleth.Functions.Compose"
+                        c:g-ref="shibboleth.PrincipalNameLookup.Subject"
+                        c:f-ref="shibboleth.ChildLookup.SubjectContext" />
+                </entry>
+             </map>
+        </property>
+    </bean>
     
 
 </beans>
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 3ffcd13..8e26f81 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
@@ -87,23 +87,23 @@
 
     <bean id="AddUserVerificationRequired" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.AddUserVerificationRequirement" scope="prototype"
-        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
         p:userVerificationRequirement="required" />
 
     <bean id="AddUserVerificationNotRequired" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.AddUserVerificationRequirement" scope="prototype"
-        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
         p:userVerificationRequirement="discouraged" />
 
     <bean id="LookupRegisteredCredentials" scope="prototype" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
         p:triggerEventOnNoCredentials="%{idp.authn.webauthn.passwordless.signalEventOnNoCredentials:false}"
         p:noCredentialsEventId="%{idp.authn.webauthn.passwordless.noCredentialsEventId:NoRegisteredWebAuthnCredentials}"
-        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" />
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" />
 
     <bean id="GenerateServerChallenge" scope="prototype" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
-        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" />
+        p:webAuthnContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext" />
 
     <bean id="CreatePublicKeyCredentialRequestOptions" scope="prototype" parent="AbstractWebAuthnAuthenticationAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.CreatePublicKeyCredentialRequestOptions"/>
@@ -128,6 +128,10 @@
         p:writeAuditLogAction="#{%{idp.authn.webauthn.audit.enabled:%{idp.authn.audit.enabled:false}} ? getObject('WriteAuthnAuditLog') : null}"/>
 
     <!-- Audit logging beans. -->
+    
+    <bean id="AdminActionPopulateAuditContext" parent="shibboleth.authn.AbstractPopulateAuditContext"
+        p:fieldExtractors="#{getObject('shibboleth.authn.DuoOIDC.AdminActionPopulateAuditExtractors') ?: getObject('shibboleth.authn.DuoOIDC.DefaultAdminActionPopulateAuditExtractors')}"
+        p:clearAuditContext="true"/>
 
     <!-- Default audit format and extractors --> 
     <util:map id="shibboleth.authn.AuditFormattingMap">
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 55e4e53..106f601 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
@@ -100,6 +100,10 @@ idp.authn.webauthn.supportedPrincipals = \
 #idp.authn.webauthn.registration.infoMessageFunction = DefaultRegistrationInfoMessageFunction
 #idp.authn.webauthn.registration.errorMessageFunction = DefaultRegistrationErrorMessageFunction
 
+#idp.authn.webauthn.registration.audit.enabled = false
+#idp.authn.webauthn.registration.audit.category = Shibboleth-Audit.WebAuthnRegistration
+#idp.authn.webauthn.registration.audit.format = %a|%T|%u|%WebAuthnAdminAO|%WebAuthnAdminAction|%WebAuthnAdminCR|%WebAuthnAdminCA|%WebAuthnAdminAU|%UA
+
 
 #### Administrator properties
 
@@ -114,6 +118,9 @@ idp.authn.webauthn.supportedPrincipals = \
 #idp.authn.webauthn.admin.management.genericMessageID = 
 #idp.authn.webauthn.admin.management.infoMessageFunction = DefaultAdminInfoMessageFunction
 #idp.authn.webauthn.admin.management.errorMessageFunction = DefaultAdminErrorMessageFunction
+#idp.authn.webauthn.admin.management.audit.enabled = false
+#idp.authn.webauthn.admin.management.audit.format = %a|%T|%u|%WebAuthnAdminAO|%WebAuthnAdminAction|%WebAuthnAdminCR|%WebAuthnAdminAU|%UA
+#idp.authn.webauthn.admin.management.audit.category = Shibboleth-Audit.WebAuthnManagment
 
 #### Authentication properties
 
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
index c16626b..d42e105 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-register.vm
@@ -21,7 +21,7 @@
 ## Add CSP directives
 #set ($areYouSure =  "return confirm('#springMessageText('idp.webauthn.register.credential.remove.confirm', 'Are you sure')');")
 #set ($nonce = $cspNonce.generateIdentifier())
-##$response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'; script-src-elem 'nonce-$nonce'; script-src-attr 'unsafe-hashes' 'sha256-$cspDigester.apply($areYouSure)'")
+$response.addHeader("Content-Security-Policy", "default-src 'none'; style-src 'self'; img-src 'self'; script-src-elem 'nonce-$nonce'; script-src-attr 'unsafe-hashes' 'sha256-$cspDigester.apply($areYouSure)'")
 
 ##
 <!DOCTYPE html>

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


More information about the commits mailing list