[java-idp-plugin-webauthn] branch main updated: Update views. Add more Yubico types to client interface

Phil Smart philip.smart at jisc.ac.uk
Wed Dec 6 14:05:49 UTC 2023


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

The following commit(s) were added to refs/heads/main by this push:
     new dcf3cdf  Update views. Add more Yubico types to client interface
dcf3cdf is described below

commit dcf3cdfbe1940c71f29e08ac2f419bbd213306ed
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Dec 6 14:05:46 2023 +0000

    Update views. Add more Yubico types to client interface
    
     - Now works end to end for discoverable keys
---
 ...Action.java => AbstractWebAuthnBaseAction.java} |  92 +++++---
 .../AbstractWebAuthnRegistrationAction.java        |  33 ++-
 .../webauthn/WebAuthnAuthenticationClient.java     |  29 ++-
 .../webauthn/context/BaseWebAuthnContext.java      |  54 +++++
 .../context/WebAuthnAuthenticationContext.java     |  38 +---
 .../context/WebAuthnRegistrationContext.java       |  16 +-
 .../CreatePublicKeyCredentialCreationOptions.java  |   3 +-
 ...actAuthenticatorAttestationFromFormRequest.java |  17 +-
 .../admin/impl/LookupRegisteredCredentials.java    |  63 ++++++
 .../admin/impl/StorePublicKeyCredential.java       |  41 +---
 .../ValidateAuthenticatorAttestationResponse.java  |  11 +-
 .../impl/YubicoWebauthnAuthenticationClient.java   |  69 +++---
 ...tractAuthenticatorAssertionFromFormRequest.java |  28 ++-
 .../webauthn/impl/GenerateServerChallenge.java     |  50 +----
 .../PopulateWebAuthnAuthenticationContext.java     |   2 +-
 .../webauthn/impl/ValidateWebAuthnAssertion.java   |  16 +-
 .../webauthn-registration-beans.xml                |  11 +-
 .../webauthn-registration-flow.xml                 |   1 +
 .../authn/WebAuthn/webauthn-abstract-beans.xml     |  13 +-
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |  13 +-
 .../idp/flows/authn/WebAuthn/webauthn-flow.xml     |   2 +
 .../webauthn/views/webauthn-auth-old-backup.vm     | 241 +++++++++++++++++++++
 .../plugin/authn/webauthn/views/webauthn-authn.vm  |  28 ++-
 .../authn/webauthn/views/webauthn-register.vm      |  47 ++--
 .../authn/webauthn/views/webauthn-selector.vm      |   2 +-
 25 files changed, 683 insertions(+), 237 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
similarity index 58%
copy from webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
copy to webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
index 017d9e8..a1034d1 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
@@ -27,9 +27,12 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import com.yubico.webauthn.CredentialRepository;
+
+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.NonnullAfterInit;
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.component.ComponentInitializationException;
@@ -37,33 +40,38 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * <p>A base class for WebAuthn registration related admin actions.</p>
+ * <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 WebAuthnRegistrationContext}.</p>
+ * and makes available the {@link BaseWebAuthnContext}.</p>
  * 
  * <p>WebAuthn registration action implementations should override the
- * {@link #doExecute(ProfileRequestContext, WebAuthnRegistrationContext)} method.</p>
+ * {@link #doExecute(ProfileRequestContext, BaseWebAuthnContext)} 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 AbstractProfileAction {
+//TODO a few abstract actions, remove overlap
+public abstract class AbstractWebAuthnBaseAction extends AbstractProfileAction {
     
     /** Class logger. */
-    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(AbstractWebAuthnRegistrationAction.class);
+    @Nonnull @NotEmpty private final Logger log = LoggerFactory.getLogger(AbstractWebAuthnBaseAction.class);
     
-    /** Lookup strategy to locate the webauthn registration context. */
+    /** Lookup strategy to locate the WebAuthn base context. */
     @Nonnull 
-    private Function<ProfileRequestContext,WebAuthnRegistrationContext> webauthnRegistrationContextLookupStrategy;
+    private Function<ProfileRequestContext,BaseWebAuthnContext> webAuthnBaseContextLookupStrategy;
     
-    /** The WebAuthn registration Context.*/
-    @NonnullBeforeExec private WebAuthnRegistrationContext webauthnRegistrationContext;
+    /** The WebAuthn base Context.*/
+    @NonnullBeforeExec private BaseWebAuthnContext webAuthnBaseContext;
     
     /** The WebAuthn client to use.*/
     @NonnullBeforeExec private WebAuthnAuthenticationClient webAuthnClient;
     
+    /** The credential repository to store valid credentials in.*/
+    // TODO replace with an adaptor to the storage service?
+    @NonnullAfterInit private CredentialRepository credentialRepository;
+    
     
     /**
      * Set the WebAuthn client used to handle registration and authentication ceremonies.
@@ -87,25 +95,46 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
         
     
     /** Constructor.*/
-    protected AbstractWebAuthnRegistrationAction() {
-        //prc -> WebAuthnContext
-        webauthnRegistrationContextLookupStrategy = new ChildContextLookup<>(WebAuthnRegistrationContext.class);
+    protected AbstractWebAuthnBaseAction() {
+        //prc -> WebAuthnBaseContext
+        webAuthnBaseContextLookupStrategy = new ChildContextLookup<>(BaseWebAuthnContext.class).
+                compose(new ChildContextLookup<>(AuthenticationContext.class));
     }
     
     
     /**
-     * Set WebAuthn registration context lookup strategy to use.
+     * Set WebAuthn base context lookup strategy to use.
      * 
      * @param strategy lookup strategy
      */
-    public void setWebauthnRegistrationContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext,WebAuthnRegistrationContext> strategy) {
+    public void setWebAuthnBaseContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,BaseWebAuthnContext> strategy) {
         checkSetterPreconditions();
 
-        webauthnRegistrationContextLookupStrategy = 
+        webAuthnBaseContextLookupStrategy = 
                 Constraint.isNotNull(strategy, "WebauthnContextLookuplookup strategy cannot be null");
     }
     
+    
+    /**
+     * Set the credential repository used to store WebAuthn credentials.
+     *  
+     * @param repository The respository to set.
+     */
+    public void setCredentialRepository(@Nonnull final CredentialRepository 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.
+     */
+    public CredentialRepository getCredentialRepository() {
+        return credentialRepository;
+    }
+    
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -114,6 +143,9 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
         if (webAuthnClient == null) {
             throw new ComponentInitializationException("WebAuthn Client can not be null");
         }
+        if (credentialRepository == null) {
+            throw new ComponentInitializationException("CredentialRepository can not be null");
+        }
     }
     
     /** {@inheritDoc} */
@@ -124,18 +156,18 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
             return false;
         }
       
-        webauthnRegistrationContext = webauthnRegistrationContextLookupStrategy.apply(profileRequestContext);
-        if (webauthnRegistrationContext == null) {
-            log.warn("{} No WebAuthn registration context returned by lookup strategy",getLogPrefix());
+        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;
             
         }        
-        return doPreExecute(profileRequestContext, webauthnRegistrationContext);
+        return doPreExecute(profileRequestContext, webAuthnBaseContext);
     }
     
     /**
-     * Delegates to {@link #doExecute(ProfileRequestContext, WebAuthnAuthenticationContext)} to perform the 
+     * 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
@@ -143,31 +175,31 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
     @Override
     protected final void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
         
-        doExecute(profileRequestContext, webauthnRegistrationContext);
+        doExecute(profileRequestContext, webAuthnBaseContext);
     }
     
     /**
-     * Performs this admin action's pre-execute step. Default implementation just returns true.
+     * Performs this action's pre-execute step. Default implementation just returns true.
      * 
      * @param profileRequestContext the current IdP profile request context
-     * @param context the WebAuthn registration context
+     * @param context the WebAuthn base context
      * 
      * @return true iff execution should continue
      */
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnRegistrationContext context) {
+            @Nonnull final BaseWebAuthnContext context) {
         return true;
     }
     
     /**
-     * Performs this webauthn authentication action using the supplied webauthn context. Implementations
+     * 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 registration context
+     * @param context the WebAuthn base context
      */
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnRegistrationContext context) {
+            @Nonnull final BaseWebAuthnContext context) {
         
     }
 
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
index 017d9e8..3eb3f2e 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnRegistrationAction.java
@@ -27,9 +27,11 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import com.yubico.webauthn.CredentialRepository;
+
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
 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;
 import net.shibboleth.shared.component.ComponentInitializationException;
@@ -64,6 +66,10 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
     /** The WebAuthn client to use.*/
     @NonnullBeforeExec private WebAuthnAuthenticationClient webAuthnClient;
     
+    /** The credential respository to store valid credentials in.*/
+    // TODO replace with an adaptor to the storage service?
+    @NonnullAfterInit private CredentialRepository credentialRepository;
+    
     
     /**
      * Set the WebAuthn client used to handle registration and authentication ceremonies.
@@ -106,6 +112,26 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
                 Constraint.isNotNull(strategy, "WebauthnContextLookuplookup strategy cannot be null");
     }
     
+    
+    /**
+     * Set the credential repository used to store webauthn credentials.
+     *  
+     * @param repository The respository to set.
+     */
+    public void setCredentialRepository(@Nonnull final CredentialRepository 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.
+     */
+    public CredentialRepository getCredentialRepository() {
+        return credentialRepository;
+    }
+    
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -114,6 +140,9 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
         if (webAuthnClient == null) {
             throw new ComponentInitializationException("WebAuthn Client can not be null");
         }
+        if (credentialRepository == null) {
+            throw new ComponentInitializationException("CredentialRepository can not be null");
+        }
     }
     
     /** {@inheritDoc} */
@@ -135,7 +164,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
     }
     
     /**
-     * Delegates to {@link #doExecute(ProfileRequestContext, WebAuthnAuthenticationContext)} to perform the 
+     * 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
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
index 53b820e..d44024d 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/WebAuthnAuthenticationClient.java
@@ -1,12 +1,20 @@
 package net.shibboleth.idp.plugin.authn.webauthn;
 
+import java.util.Set;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 
 import com.yubico.webauthn.AssertionResult;
 import com.yubico.webauthn.RegistrationResult;
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
 import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 
 /**
@@ -27,8 +35,9 @@ public interface WebAuthnAuthenticationClient {
       * @param username the username of the username that has been pre-identified. Can be null
       *                 if no user has been identified, and the IdP is requesting the client discover
       *                 the credential.  
-      * @param userHandle an opaque user.id used to map public key credentials to user accounts and vice-versa.
-      * @param the challenge used when creating new credentials
+      * @param allowCredentials the set of public key credentials acceptable to the IdP in descending order of 
+      *                 preference
+      * @param challenge the challenge the authenticator signs along with other data to produce an assertion
       * 
       * @return a PublicKeyCredentialRequestOptions object
       * 
@@ -36,7 +45,7 @@ public interface WebAuthnAuthenticationClient {
       *         
       */
      @Nonnull PublicKeyCredentialRequestOptions createAuthenticationRequest(@Nullable final String username, 
-             @Nullable final byte[] userHandle, @Nonnull final byte[] challenge) 
+             @Nullable final Set<PublicKeyCredentialDescriptor> allowCredentials, @Nonnull final byte[] challenge) 
                      throws WebAuthnAuthenticationClientException;
      
      /**
@@ -53,18 +62,19 @@ public interface WebAuthnAuthenticationClient {
       *         
       * @throws WebAuthnAuthenticationClientException if there is an error generating the creation request
       */
-     @Nonnull PublicKeyCredentialCreationOptions createRegistrationRequest(@Nullable final String username, 
+     @Nonnull PublicKeyCredentialCreationOptions createRegistrationRequest(
+             @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, @Nullable final String username, 
              @Nullable final byte[] userHandle, @Nonnull final byte[] challenge) 
                      throws WebAuthnAuthenticationClientException;
      
      
      /**
-      * Validate the Authenticator Assertion Response from an authentication request
+      * Validate the Authenticator Assertion Response from an authentication request.
       * 
       * @param usernmae ....TODO
       * @param userHandle ....TODO
       * @param publicKeyCredentialRequestOptions the options used when generating an assertion for authentication.
-      * @param authenticatorAssertionResponse the JSON representation of the assertion response.
+      * @param authenticatorAssertionResponse the assertion response.
       * 
       * @return an assertion result if the assertion was valid.
       * 
@@ -73,8 +83,8 @@ public interface WebAuthnAuthenticationClient {
      AssertionResult validateAuthenticatorAssertionResponse(@Nullable final String username, 
              @Nullable final byte[] userHandle, 
              @Nonnull final PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions, 
-             @Nonnull final String authenticatorAssertionResponse) 
-             throws AssertionFailureException;
+             @Nonnull final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+             authenticatorAssertionResponse) throws AssertionFailureException;
 
 
     /**
@@ -89,6 +99,7 @@ public interface WebAuthnAuthenticationClient {
      */
      RegistrationResult validateAuthenticatorAttestationResponse(
              @Nonnull final PublicKeyCredentialCreationOptions publicKeyCredentialCreationOptions, 
-             @Nonnull final String authenticatorAttestationResponse) throws RegistrationFailureException;       
+             @Nonnull final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> 
+             authenticatorAttestationResponse) throws RegistrationFailureException;       
 
 }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
index 4cf3a25..3632834 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/BaseWebAuthnContext.java
@@ -14,11 +14,15 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.context;
 
+import java.util.Set;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
 
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
+
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
 import net.shibboleth.shared.logic.Constraint;
@@ -28,12 +32,62 @@ import net.shibboleth.shared.logic.Constraint;
  */
 public class BaseWebAuthnContext extends BaseContext {
     
+    /** The original username. */
+    @Nullable private String username;    
+    
+    /** 
+     * Credentials that have already been registered with the IdP. The authenticator should use these to avoid creating
+     * duplicate credentials during registration, or to tell the browser which credentials to use during authentication.
+     */
+    @Nullable private Set<PublicKeyCredentialDescriptor> existingCredentials;
+    
     /** The challenge sent to the authenticator in both registration and authentication ceremonies.*/
     @Nullable private byte[] serverChallenge;  
     
     /** The userhandle supplied to the authenticator during registration. As generated by the IdP.*/
     @Nullable private byte[] userHandle;
     
+    /**
+     * Gets the username.
+     * 
+     * @return the username
+     */
+    @Nullable public String getUsername() {
+        return username;
+    }
+
+    /**
+     * Sets the username and resets the transformed version to be identical.
+     * 
+     * @param name the username
+     * 
+     * @return this context
+     */
+    @Nonnull public BaseWebAuthnContext setUsername(@Nullable final String name) {
+        username = name;
+        //transformedUsername = name;
+        return this;
+    }
+
+    /**
+     * Set the credentials that have already been registered with the IdP.
+     * 
+     * @param credentials the set of credentials
+     */
+    public void setExistingCredentials(@Nullable final Set<PublicKeyCredentialDescriptor> credentials) {
+        existingCredentials = credentials;
+        
+    }
+    
+    /**
+     * Get the  credentials that have already been registered with the IdP.
+     * 
+     * @return the excluded credentials.
+     */
+    public Set<PublicKeyCredentialDescriptor> getExistingCredentials() {
+        return existingCredentials;
+    }
+    
     /**
      * Get the server challenge sent to the authenticator.
      * 
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java
index 7729c59..153a7d5 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnAuthenticationContext.java
@@ -4,6 +4,9 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.NotThreadSafe;
 
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 
 import net.shibboleth.shared.logic.Constraint;
@@ -19,11 +22,9 @@ public final class WebAuthnAuthenticationContext extends BaseWebAuthnContext {
     /** The credential identifier generated by the authenticator.*/
     @Nullable private byte[] credentialId;
     
-    /** The original username. */
-    @Nullable private String username;
-    
     /** An assertion response that is the result of an authentication.*/
-    @Nullable private String authenticatorAssertionResponse;
+    @Nullable 
+    private PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> authenticatorAssertionResponse;
    
     /** The public key credential request options for authentication.*/ 
     @Nullable private PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions;
@@ -35,28 +36,6 @@ public final class WebAuthnAuthenticationContext extends BaseWebAuthnContext {
     /** The userhandle supplied to the authenticator during registration. As generated by the IdP.*/
     @Nullable private byte[] userHandle;
   
-    /**
-     * Gets the username.
-     * 
-     * @return the username
-     */
-    @Nullable public String getUsername() {
-        return username;
-    }
-
-    /**
-     * Sets the username and resets the transformed version to be identical.
-     * 
-     * @param name the username
-     * 
-     * @return this context
-     */
-    @Nonnull public WebAuthnAuthenticationContext setUsername(@Nullable final String name) {
-        username = name;
-        //transformedUsername = name;
-        return this;
-    }
-    
     
     /**
      * Set the public key, as a byte array, in COSE_Key format.
@@ -113,7 +92,9 @@ public final class WebAuthnAuthenticationContext extends BaseWebAuthnContext {
      *  
      * @param assertion The authenticator assertion response to set.
      */
-    public WebAuthnAuthenticationContext setAuthenticatorAssertionResponse(@Nullable final String assertion) {
+    public WebAuthnAuthenticationContext setAuthenticatorAssertionResponse(
+            @Nullable final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            assertion) {
         authenticatorAssertionResponse = assertion;
         return this;
     }
@@ -123,7 +104,8 @@ public final class WebAuthnAuthenticationContext extends BaseWebAuthnContext {
      * 
      * @return the authenticator a ssertion response.
      */
-    @Nullable public String getAuthenticatorAssertionResponse() {
+    @Nullable public PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs>
+        getAuthenticatorAssertionResponse() {
         return authenticatorAssertionResponse;
     }
         
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
index d2b7348..e7d0cda 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/WebAuthnRegistrationContext.java
@@ -5,6 +5,9 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.NotThreadSafe;
 
 import com.yubico.webauthn.RegistrationResult;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 
 
@@ -24,7 +27,8 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
     @Nullable private byte[] existingPublicKey;    
     
     /** An assertion response that is the result of an authentication.*/
-    @Nullable private String authenticatorAttestationResponse;
+    @Nullable private PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> 
+        authenticatorAttestationResponse;
     
     /** The public key credential creation options for registration.*/ 
     @Nullable private PublicKeyCredentialCreationOptions publicKeyCredentialCreationOptions;
@@ -44,7 +48,8 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
      * 
      * @return Returns the authenticator attestation response.
      */
-    @Nullable public String getAuthenticatorAttestationResponse() {
+    @Nullable public PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> 
+            getAuthenticatorAttestationResponse() {
         return authenticatorAttestationResponse;
     }
     
@@ -53,7 +58,9 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
      * 
      * @param authenticatorAttestationResponse The authenticatorAttestationResponse to set.
      */
-    public WebAuthnRegistrationContext setAuthenticatorAttestationResponse(@Nullable final String attestation) {
+    public WebAuthnRegistrationContext setAuthenticatorAttestationResponse(
+            @Nullable final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> 
+            attestation) {
         authenticatorAttestationResponse = attestation;
         return this;
     }
@@ -63,6 +70,7 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
      * 
      * @return the username
      */
+    @Override
     @Nullable public String getUsername() {
         return username;
     }
@@ -74,6 +82,7 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
      * 
      * @return this context
      */
+    @Override
     @Nonnull public WebAuthnRegistrationContext setUsername(@Nullable final String name) {
         username = name;
         //transformedUsername = name;
@@ -139,4 +148,5 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
         return this;
     }
 
+
 }
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 49ac254..e97ba8c 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
@@ -83,7 +83,8 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnRe
         
         try {                
             final PublicKeyCredentialCreationOptions pkCredCreationOptions = 
-                    client.createRegistrationRequest(context.getUsername(), context.getUserHandle(), challenge);
+                    client.createRegistrationRequest(context.getExistingCredentials(),context.getUsername(), 
+                            context.getUserHandle(), challenge);
             context.setPublicKeyCredentialCreationOptions(pkCredCreationOptions);
             //convert to JSON
             context.setPublicKeyCredentialCreationOptionsJSON(
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java
index 30551b9..dae40fe 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/ExtractAuthenticatorAttestationFromFormRequest.java
@@ -17,6 +17,8 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
+import java.io.IOException;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
@@ -25,6 +27,9 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 
 import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -110,7 +115,17 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
             return;
         }
 
-        context.setAuthenticatorAttestationResponse(pkCredAttestationJson);
+        try {
+            final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> 
+                pkCredAttestation = PublicKeyCredential.parseRegistrationResponseJson(pkCredAttestationJson);
+            context.setAuthenticatorAttestationResponse(pkCredAttestation);
+        } catch (final IOException e) {
+            log.warn("{} Could not convert AuthenticatorAttestationResponse from form", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+            return;
+        }
+
+       
 
     }
 
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupRegisteredCredentials.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupRegisteredCredentials.java
new file mode 100644
index 0000000..35d8c1a
--- /dev/null
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupRegisteredCredentials.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.admin.impl;
+
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnBaseAction;
+import net.shibboleth.idp.plugin.authn.webauthn.context.BaseWebAuthnContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Action that lookups existing registered credentials
+ */
+public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(LookupRegisteredCredentials.class);
+    
+    /** {@inheritDoc} */
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final BaseWebAuthnContext context) {
+        
+        final String username = context.getUsername();
+
+        if (username == null) {
+            log.error("Unable to find username in registration context");
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
+            return;
+        } 
+        
+        final Set<PublicKeyCredentialDescriptor> credentials =
+                getCredentialRepository().getCredentialIdsForUsername(username);       
+
+        log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(), 
+                credentials != null ? credentials.size() : "0", username);
+        
+        context.setExistingCredentials(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 1d5ba1a..2c1c36c 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
@@ -31,7 +31,6 @@ import org.opensaml.storage.StorageSerializer;
 import org.opensaml.storage.StorageService;
 import org.slf4j.Logger;
 
-import com.yubico.webauthn.CredentialRepository;
 import com.yubico.webauthn.RegisteredCredential;
 import com.yubico.webauthn.RegistrationResult;
 import com.yubico.webauthn.data.AuthenticatorTransport;
@@ -50,6 +49,9 @@ import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
+/**
+ * An action that stores the public key credential into the credential repository.
+ */
 public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction {
 
     /** Class logger. */
@@ -60,9 +62,6 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
     @NonnullAfterInit
     private StorageService storageService;    
     
-    /** The credential respository to store valid credentials in.*/
-    // TODO replace with an adaptor to the storage service?
-    @NonnullAfterInit private CredentialRepository credentialRepository;
 
     /** Storage record serializer. */
     @Nonnull
@@ -72,17 +71,6 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
     public StorePublicKeyCredential() {
         serializer = new WebauthnPublicKeyCredentialStorageSerializer();
     }
-    
-    /**
-     * Set the credential repository used to store the valid webauthn credential.
-     *  
-     * @param credRepository The credRepository to set.
-     */
-    public void setCredentialRepository(@Nonnull final CredentialRepository repository) {
-        checkSetterPreconditions();
-        credentialRepository = Constraint.isNotNull(repository, "Credential respository can not be null");
-    }
-
 
     /**
      * Set the {@link StorageService} back-end to use.
@@ -101,23 +89,12 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
     @Override
     protected void doInitialize() throws ComponentInitializationException {
         super.doInitialize();
-        
-        if (credentialRepository == null) {
-            throw new ComponentInitializationException("Credential respository can not be null");
-        }
 
         if (storageService == null) {
             throw new ComponentInitializationException("StorageService cannot be null");
         }
     }
 
-    // TODO maybe not needed.
-    @Override
-    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext,
-            @Nonnull final WebAuthnRegistrationContext context) {
-        return true;
-    }
-
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final WebAuthnRegistrationContext context) {
@@ -125,35 +102,35 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
         final String username = context.getUsername();
 
         if (username == null) {
-            log.error("Unable to find username in registration response");
+            log.error("Unable to find username in registration context");
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
             return;
         }        
         final RegistrationResult registrationResult = context.getRegistrationResult();
         if (registrationResult == null) {
-            log.error("Unable to find registration information in registration response");
+            log.error("Unable to find registration information in registration context");
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
             return;
         } 
         try {
             final RegisteredCredential credential = RegisteredCredential.builder()
                     .credentialId(registrationResult.getKeyId().getId())
-                    .userHandle(ByteArray.fromBase64("bm90YWhhbmRsZQ=="))
+                    .userHandle(new ByteArray(context.getUserHandle()))
                     .publicKeyCose(registrationResult.getPublicKeyCose())
                     .build();
             
             final UserIdentity user = UserIdentity.builder()
                     .name(username)
                     .displayName(username)
-                    .id(ByteArray.fromBase64("bm90YWhhbmRsZQ=="))
+                    .id(new ByteArray(context.getUserHandle()))
                     .build();
             
             // TODO fixup the record we will use to store registrations
-            final CredentialRegistration registration = new CredentialRegistration(user, Optional.of("Nickanme"), 
+            final CredentialRegistration registration = new CredentialRegistration(user, Optional.of("Nickname"), 
                     new TreeSet<AuthenticatorTransport>(), Instant.now(), credential, Optional.empty());
             
             // TODO should not need a cast here when we sort out the storage
-            if (credentialRepository instanceof final InMemoryRegistrationStorage inMemoryRepo) {
+            if (getCredentialRepository() instanceof final InMemoryRegistrationStorage inMemoryRepo) {
                 inMemoryRepo.addRegistrationByUsername(username, registration);
                 log.debug("{} Added public key credential registration for user '{}' and key '{}' ", 
                         getLogPrefix(), username, registrationResult.getKeyId().getId().getBase64Url());
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 12e3bc2..b6becb0 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
@@ -24,6 +24,9 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.yubico.webauthn.RegistrationResult;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -33,7 +36,6 @@ import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationCont
 import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.primitive.StringSupport;
 
 /**
  * Validate the public key registration attempt. If valid store it inside the credential repository.
@@ -48,7 +50,8 @@ public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnRe
     @NonnullBeforeExec @NotEmpty private PublicKeyCredentialCreationOptions pkCredCreationOptions;
     
     /** The stashed authenticator response.*/
-    @NonnullBeforeExec @NotEmpty private String attestation;
+    @NonnullBeforeExec
+    private PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation;
 
     
     /** {@inheritDoc} */
@@ -61,8 +64,8 @@ public class ValidateAuthenticatorAttestationResponse extends AbstractWebAuthnRe
         }
        
         attestation = context.getAuthenticatorAttestationResponse();
-        if (StringSupport.trimOrNull(attestation) == null) {
-            log.error("{} public key credential was null", getLogPrefix());
+        if (attestation == null) {
+            log.error("{} authenticator attestation response was null", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return false;
         }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
index a0b268e..fd2f271 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClient.java
@@ -14,11 +14,12 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.client.impl;
 
-import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -34,12 +35,17 @@ import com.yubico.webauthn.FinishRegistrationOptions;
 import com.yubico.webauthn.RegistrationResult;
 import com.yubico.webauthn.RelyingParty;
 import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.AuthenticatorAttestationResponse;
+import com.yubico.webauthn.data.AuthenticatorSelectionCriteria;
 import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
 import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
 import com.yubico.webauthn.data.PublicKeyCredentialParameters;
 import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
+import com.yubico.webauthn.data.ResidentKeyRequirement;
 import com.yubico.webauthn.data.UserIdentity;
 import com.yubico.webauthn.data.UserVerificationRequirement;
 import com.yubico.webauthn.exception.RegistrationFailedException;
@@ -95,7 +101,8 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
 
     @Override
     public PublicKeyCredentialRequestOptions createAuthenticationRequest(@Nullable final String username, 
-            final byte[] userHandle, final byte[] challenge) throws WebAuthnAuthenticationClientException {
+            @Nullable final Set<PublicKeyCredentialDescriptor> allowCredentials, final byte[] challenge) 
+                    throws WebAuthnAuthenticationClientException {
  
         //set default to preferred.
         UserVerificationRequirement userVerificationRequirement = UserVerificationRequirement.PREFERRED;
@@ -103,24 +110,15 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
             //then require user verification? makes sense, but is that part of the spec?
             userVerificationRequirement = UserVerificationRequirement.REQUIRED;
         }
-//            final AssertionRequest assertion =
-//                    rp.startAssertion(StartAssertionOptions.builder()
-//                            .username(Optional.ofNullable(username))
-//                            .userVerification(userVerificationRequirement)
-//                            .build());
+        
+        // The order of these credentials becomes important
+        final List<PublicKeyCredentialDescriptor> listOfCredentials = allowCredentials == null ? null : 
+            new ArrayList<>(allowCredentials);
+        
         final PublicKeyCredentialRequestOptions request = PublicKeyCredentialRequestOptions.builder()
                     .challenge(new ByteArray(challenge))
                     .rpId(rp.getIdentity().getId())
-//                        .allowCredentials(
-//                            OptionalUtil.orElseOptional(
-//                                    startAssertionOptions.getUsername(),
-//                                    () ->
-//                                        startAssertionOptions
-//                                            .getUserHandle()
-//                                            .flatMap(credentialRepository::getUsernameForUserHandle))
-//                                .map(
-//                                    un ->
-//                                        new ArrayList<>(credentialRepository.getCredentialIdsForUsername(un))))
+                    .allowCredentials(Optional.ofNullable(listOfCredentials))
 //                        .extensions(
 //                            startAssertionOptions
 //                                .getExtensions()
@@ -137,7 +135,8 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
     
     /** {@inheritDoc} */
     @Override
-    public PublicKeyCredentialCreationOptions createRegistrationRequest(final String username, 
+    public PublicKeyCredentialCreationOptions createRegistrationRequest(
+            @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, final String username, 
             final byte[] userHandle, final byte[] challenge) throws WebAuthnAuthenticationClientException {
        
         //set default to preferred.
@@ -146,6 +145,7 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
             //then require user verification? makes sense, but is that part of the spec?
             userVerificationRequirement = UserVerificationRequirement.REQUIRED;
         }
+        final ResidentKeyRequirement residentKeyRquirement = ResidentKeyRequirement.REQUIRED;
         final UserIdentity identity = 
                 UserIdentity.builder().name(username).displayName(username).id(new ByteArray(userHandle)).build();
         
@@ -154,10 +154,11 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
                     .user(identity)
                     .challenge(new ByteArray(challenge))
                     .pubKeyCredParams(preferredPublickeyParams)
-                    .excludeCredentials(Optional.empty())
-//                        .excludeCredentials(
-//                            credentialRepository.getCredentialIdsForUsername(
-//                                startRegistrationOptions.getUser().getName()))
+                    .excludeCredentials(excludeCredentials)
+                    .authenticatorSelection(AuthenticatorSelectionCriteria.builder()
+                            .userVerification(userVerificationRequirement)
+                            .residentKey(residentKeyRquirement)
+                            .build())
 //                        .authenticatorSelection(startRegistrationOptions.getAuthenticatorSelection())
 //                        .extensions(
 //                            startRegistrationOptions
@@ -178,7 +179,8 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
     public AssertionResult validateAuthenticatorAssertionResponse(@Nullable final String username, 
             @Nullable final byte[] userHandle,
             @Nonnull final PublicKeyCredentialRequestOptions publicKeyCredentialRequestOptions,
-            @Nonnull final String authenticatorAssertionResponse) throws AssertionFailureException {
+            @Nonnull final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            authenticatorAssertionResponse) throws AssertionFailureException {
         
         try {
             
@@ -187,14 +189,12 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
                 .userHandle(Optional.ofNullable(userHandle != null ? new ByteArray(userHandle) : null))
                 .username(Optional.ofNullable(username))
                 .build();
-            
-            final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> pkCred = 
-                    PublicKeyCredential.parseAssertionResponseJson(authenticatorAssertionResponse);
-            log.trace("Client Data '{}'",pkCred.getResponse().getClientData());
-            log.trace("Signature '{}'",pkCred.getResponse().getSignature());
+  
+            log.trace("Client Data '{}'",authenticatorAssertionResponse.getResponse().getClientData());
+            log.trace("Signature '{}'",authenticatorAssertionResponse.getResponse().getSignature());
             final AssertionResult result = rp.finishAssertion(FinishAssertionOptions.builder()
                     .request(requestAssertion)
-                    .response(pkCred)
+                    .response(authenticatorAssertionResponse)
                     .build());
             if (result == null) {
                 throw new AssertionFailureException("Unable to validate authenticator assertion");
@@ -215,19 +215,18 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
     @Override
     public RegistrationResult validateAuthenticatorAttestationResponse(
             @Nonnull final PublicKeyCredentialCreationOptions publicKeyCredentialCreationOptions, 
-            @Nonnull final String authenticatorAttestationResponse) throws RegistrationFailureException {
+            @Nonnull final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> 
+            authenticatorAttestationResponse) throws RegistrationFailureException {
         
         try {            
-            log.trace("Public Key Credential to validate '{}'", authenticatorAttestationResponse);
-            final var publicKeyRegistration = 
-                    PublicKeyCredential.parseRegistrationResponseJson(authenticatorAttestationResponse);   
+            log.trace("Public Key Credential to validate '{}'", authenticatorAttestationResponse);  
             
             return rp.finishRegistration(FinishRegistrationOptions.builder()
                     .request(publicKeyCredentialCreationOptions)
-                    .response(publicKeyRegistration)
+                    .response(authenticatorAttestationResponse)
                     .build());
 
-        } catch (final RegistrationFailedException | IOException e) {
+        } catch (final RegistrationFailedException e) {
            throw new RegistrationFailureException(e);
         }
     }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractAuthenticatorAssertionFromFormRequest.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractAuthenticatorAssertionFromFormRequest.java
index d9ce6e1..ffa2529 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractAuthenticatorAssertionFromFormRequest.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ExtractAuthenticatorAssertionFromFormRequest.java
@@ -17,6 +17,8 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
+import java.io.IOException;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
@@ -25,6 +27,9 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 
 import jakarta.servlet.http.HttpServletRequest;
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -102,16 +107,25 @@ public class ExtractAuthenticatorAssertionFromFormRequest extends AbstractWebAut
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return;
         }
-        // TODO correct object type
-        final String pkCredJson = extractAuthenticatorAssertionResponse(request);         
-        if (pkCredJson == null) {
-            log.debug("Public key assertion not found in HTTP request: '{}'",pkCredJson);
+
+        final String pkCredAssertionJson = extractAuthenticatorAssertionResponse(request);         
+        if (pkCredAssertionJson == null) {
+            log.debug("{} Public key assertion not found in HTTP request",getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return;
         }   
-        log.trace("Public key assertion found: '{}'",pkCredJson);
-       
-        context.setAuthenticatorAssertionResponse(pkCredJson);
+        log.trace("{} Authenticator assertion response: '{}'",getLogPrefix(),pkCredAssertionJson);
+
+        try {
+            final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion = 
+                    PublicKeyCredential.parseAssertionResponseJson(pkCredAssertionJson);
+            context.setAuthenticatorAssertionResponse(assertion);
+        } catch (final IOException e) {
+            log.debug("{} Authenticator assertion could not be converted to the correct type",getLogPrefix(), e);
+            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 a6b8490..1bf358c 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
@@ -19,21 +19,16 @@ package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
-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.AbstractWebAuthnBaseAction;
 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.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
@@ -45,51 +40,14 @@ import net.shibboleth.shared.primitive.LoggerFactory;
  * @event {@link net.shibboleth.idp.authn.AuthnEventIds#AUTHN_EXCEPTION}
  * @post See above.
  */
-public class GenerateServerChallenge extends AbstractProfileAction {
+public class GenerateServerChallenge extends AbstractWebAuthnBaseAction {
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(GenerateServerChallenge.class);
-    
-    /** Lookup strategy to locate the WebAuthn base context. */
-    @Nonnull private Function<ProfileRequestContext,BaseWebAuthnContext> webAuthnBaseContextLookupStrategy;
-    
-    @NonnullBeforeExec private BaseWebAuthnContext context;
-    
-    /** Constructor.*/
-    protected GenerateServerChallenge() {
-        //prc -> ac -> base context
-        webAuthnBaseContextLookupStrategy = new ChildContextLookup<>(BaseWebAuthnContext.class).
-                compose(new ChildContextLookup<>(AuthenticationContext.class));
-    }
-    
-    /**
-     * @param webauthnBaseContextLookupStrategy The webauthnBaseContextLookupStrategy to set.
-     */
-    public void setWebAuthnBaseContextLookupStrategy(
-            @Nonnull final Function<ProfileRequestContext, BaseWebAuthnContext> strategy) {
-        checkSetterPreconditions();
-        webAuthnBaseContextLookupStrategy = Constraint.isNotNull(strategy,
-                "WebAuthnBaseContextLookupStrategy can not be null");
-    }
-    
-    /** {@inheritDoc} */
-    @Override
-    protected boolean doPreExecute(final ProfileRequestContext profileRequestContext) {
-        if (!super.doPreExecute(profileRequestContext)) {
-            return false;
-        }
-        context = webAuthnBaseContextLookupStrategy.apply(profileRequestContext);
-        if (context == null) {
-            log.warn("{} WebAuthnBaseContext is not available", getLogPrefix());
-            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-            return false;
-        }
-        return true;
-    }
-   
 
     /** {@inheritDoc} */
-    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+    @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
+            @Nonnull final BaseWebAuthnContext context) {
         
         try {
             final byte[] challenge = generateChallenge();
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
index 9849f6d..79bb7e3 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/PopulateWebAuthnAuthenticationContext.java
@@ -118,7 +118,7 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         }
-        context.setUsername(usernameLookupStrategy.apply(profileRequestContext));
+        context.setUsername(username);
 
         log.debug("Created Webauthn authentication context");
     }
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
index 293eb15..d1c5061 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidateWebAuthnAssertion.java
@@ -11,6 +11,9 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.yubico.webauthn.AssertionResult;
+import com.yubico.webauthn.data.AuthenticatorAssertionResponse;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 
 import net.shibboleth.idp.authn.AbstractValidationAction;
@@ -103,11 +106,13 @@ public class ValidateWebAuthnAssertion extends AbstractValidationAction {
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final AuthenticationContext authenticationContext) {
         
-        final String assertion = context.getAuthenticatorAssertionResponse();
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> assertion = 
+                context.getAuthenticatorAssertionResponse();
+        
+        // TODO these need to be set from lookup earlier in the context in certain types of flows e.g. non-discoverable
+        // Null username and userhandle describes a request for a discoverable credential
+        context.setUsername(null);
         
-        // TODO these need to be set from lookup earlier in the context
-        context.setUsername("not-the-username");
-        context.setUserHandle("notahandle".getBytes());
         
         if (assertion == null) {
             log.warn("{} No authenticator assertion found, {} can not authenticate ", 
@@ -118,9 +123,12 @@ public class ValidateWebAuthnAssertion extends AbstractValidationAction {
             return; 
         }
         try {
+            
             final AssertionResult result = webAuthnClient.validateAuthenticatorAssertionResponse(
                     context.getUsername(), context.getUserHandle(), publicKeyCredentialRequestOptions, assertion);
             
+            log.info("{} WebAuthn authentication succeeded for '{}'",getLogPrefix(),result.getUsername());
+            context.setUsername(result.getUsername());
             buildAuthenticationResult(profileRequestContext, authenticationContext);
             
         } catch (final AssertionFailureException e) {
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 2815d08..da08c57 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
@@ -24,16 +24,21 @@
     <bean id="shibboleth.ChildLookup.WebAuthnRegistrationContext"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext) }" />
+        
+    <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnRegistrationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.LookupRegisteredCredentials"
+        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"/>
 
-    <bean id="GenerateServerChallenge" parent="AbstractGenerateServerChallenge"
+    <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
         p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"/>
 
 
-    <bean id="GenerateUserHandle" parent="AbstractWebAuthnAuthenticationAction"
+    <bean id="GenerateUserHandle" parent="AbstractWebAuthnRegistrationAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.GenerateUserHandle" />
 
     <bean id="CreatePublicKeyCredentialCreationOptions" parent="AbstractWebAuthnRegistrationAction"
-        class=" net.shibboleth.idp.plugin.authn.webauthn.admin.impl.CreatePublicKeyCredentialCreationOptions" 
+        class="net.shibboleth.idp.plugin.authn.webauthn.admin.impl.CreatePublicKeyCredentialCreationOptions" 
         p:objectMapper-ref="shibboleth.authn.WebAuthn.JSONObjectMapper"/>
 
     <bean id="ExtractAuthenticatorAttestationFromFormRequest" parent="AbstractWebAuthnRegistrationAction"
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
index ecbef86..983a7cf 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/admin/webauthn-registration/webauthn-registration-flow.xml
@@ -25,6 +25,7 @@
     
      <action-state id="PopulateWebAuthnContext">
         <evaluate expression="PopulateWebAuthnRegistrationContext"/>
+        <evaluate expression="LookupRegisteredCredentials"/>
         <evaluate expression="GenerateServerChallenge"/>
         <evaluate expression="GenerateUserHandle"/>
         <evaluate expression="CreatePublicKeyCredentialCreationOptions"/>
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 8e6b455..9d7be7d 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
@@ -14,14 +14,13 @@
         p:webAuthnClient="#{getObject('shibboleth.authn.webauthn.DefaultWebauthnAuthenticationClientFactory')}"/>
         
     <bean id="AbstractWebAuthnRegistrationAction" scope="prototype" abstract="true"
-        p:webAuthnClient="#{getObject('shibboleth.authn.webauthn.DefaultWebauthnAuthenticationClientFactory')}"/>
-
-    <!-- Generic beans -->
-    <bean id="AbstractGenerateServerChallenge" scope="prototype" abstract="true"
-        class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge" />
-
+        p:webAuthnClient="#{getObject('shibboleth.authn.webauthn.DefaultWebauthnAuthenticationClientFactory')}"
+        p:credentialRepository="#{getObject('shibboleth.authn.webauthn.DefaultCredentialRepository')}"/>
     
-        
+    <bean id="AbstractWebAuthnBaseAction" scope="prototype" abstract="true"
+        p:webAuthnClient="#{getObject('shibboleth.authn.webauthn.DefaultWebauthnAuthenticationClientFactory')}"
+        p:credentialRepository="#{getObject('shibboleth.authn.webauthn.DefaultCredentialRepository')}"/>
+       
     
 
 </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 9c72842..6f58664 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
@@ -18,12 +18,13 @@
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext) }" />
 
-    <bean id="GenerateServerChallenge" parent="AbstractGenerateServerChallenge">
-        <property name="webAuthnBaseContextLookupStrategy">
-            <bean parent="shibboleth.Functions.Compose" c:f-ref="shibboleth.ChildLookup.AuthenticationContext"
-                c:g-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContext" />
-        </property>
-    </bean>    
+    <bean id ="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
+            parent="shibboleth.Functions.Compose" c:f-ref="shibboleth.ChildLookup.AuthenticationContext"
+            c:g-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContext" />
+
+    <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
+        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"/> 
     
     <bean id="CreatePublicKeyCredentialRequestOptions" parent="AbstractWebAuthnAuthenticationAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.CreatePublicKeyCredentialRequestOptions"
diff --git a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
index dc26b5c..0eac3c0 100644
--- a/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
+++ b/webauthn-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/authn/WebAuthn/webauthn-flow.xml
@@ -10,6 +10,8 @@
         <transition on="proceed" to="DisplayWebAuthnView" />    
     </action-state>
     
+    <!-- If passwordless and not usernameless we need a different flow here -->
+    
     
     <view-state id="DisplayWebAuthnView" view="webauthn/webauthn-authn">
         <on-render>
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-auth-old-backup.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-auth-old-backup.vm
new file mode 100644
index 0000000..81212ad
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-auth-old-backup.vm
@@ -0,0 +1,241 @@
+##
+## Velocity Template for DisplayWebauthnView view-state
+##
+## Velocity context will contain the following properties
+## flowExecutionUrl - the form action location
+## flowRequestContext - the Spring Web Flow RequestContext
+## flowExecutionKey - the SWF execution key (this is built into the flowExecutionUrl)
+## profileRequestContext - root of context tree
+## authenticationContext - context with authentication request information
+## authenticationErrorContext - context with login error state
+## webauthnContext = web authentication context
+## authenticationWarningContext - context with login warning state
+## rpUIContext - the context with SP UI information from the metadata
+## encoder - HTMLEncoder class
+## request - HttpServletRequest
+## response - HttpServletResponse
+## environment - Spring Environment object for property resolution
+## custom - arbitrary object injected by deployer
+##
+#set ($rpContext = $profileRequestContext.getSubcontext('net.shibboleth.idp.profile.context.RelyingPartyContext'))
+##
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width,initial-scale=1.0">
+        <title>#springMessageText("idp.title", "Web Login Service")</title>
+        <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/main.css">
+       
+        
+    </head>
+    
+    <!-- FIXME Externalise this-->
+    <script>
+    
+    
+    const base64abc = [
+        "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
+        "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
+        "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
+        "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
+        "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"
+    ];
+
+    
+    const base64codes = [
+        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
+        52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
+        255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+        15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
+        255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+        41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
+    ];
+    
+    function bytesToBase64(bytes) {
+            let result = '', i, l = bytes.length;
+            for (i = 2; i < l; i += 3) {
+                result += base64abc[bytes[i - 2] >> 2];
+                result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
+                result += base64abc[((bytes[i - 1] & 0x0F) << 2) | (bytes[i] >> 6)];
+                result += base64abc[bytes[i] & 0x3F];
+            }
+            if (i === l + 1) { // 1 octet yet to write
+                result += base64abc[bytes[i - 2] >> 2];
+                result += base64abc[(bytes[i - 2] & 0x03) << 4];
+                result += "==";
+            }
+            if (i === l) { // 2 octets yet to write
+                result += base64abc[bytes[i - 2] >> 2];
+                result += base64abc[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
+                result += base64abc[(bytes[i - 1] & 0x0F) << 2];
+                result += "=";
+            }
+        return result;
+    }
+       
+    function responseToObject(response) {
+        if (response.u2fResponse) {
+          return response;
+        } else {
+          let clientExtensionResults = {};
+    
+          try {
+            clientExtensionResults = response.getClientExtensionResults();
+          } catch (e) {
+            console.error('getClientExtensionResults failed', e);
+          }
+    
+          if (response.response.attestationObject) {
+            return {
+              type: response.type,
+              id: response.id,
+              response: {
+                attestationObject: Base64EncodeUrl(bytesToBase64(ensureUint8Array(response.response.attestationObject))),
+                clientDataJSON: Base64EncodeUrl(bytesToBase64(ensureUint8Array(response.response.clientDataJSON))),
+              },
+              clientExtensionResults,
+            };
+          } else {
+            return {
+              type: response.type,
+              id: response.id,
+              response: {
+                authenticatorData: Base64EncodeUrl(bytesToBase64(ensureUint8Array(response.response.authenticatorData))),
+                clientDataJSON: Base64EncodeUrl(bytesToBase64(ensureUint8Array(response.response.clientDataJSON))),
+                signature: Base64EncodeUrl(bytesToBase64(ensureUint8Array(response.response.signature))),
+                userHandle: response.response.userHandle && Base64EncodeUrl(bytesToBase64(ensureUint8Array(response.response.userHandle))),
+              },
+              clientExtensionResults,
+            };
+          }
+        }
+    }
+    
+    if (!window.PublicKeyCredential){
+        console.log("Bad client, died");
+    }
+    
+    function ensureUint8Array(arg) {
+        if (arg instanceof ArrayBuffer) {
+          return new Uint8Array(arg);
+        } else {
+          return arg;
+        }
+    }
+    /**
+     * use this to make a Base64 encoded string URL friendly, 
+     * i.e. '+' and '/' are replaced with '-' and '_' also any trailing '=' 
+     * characters are removed
+     *
+     * @param {String} str the encoded string
+     * @returns {String} the URL friendly encoded String
+     */
+    function Base64EncodeUrl(str){
+        return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/\=+$/, '');
+    }
+    
+    const publicKeyCredentialCreationOptions = {
+            challenge: Uint8Array.from(
+                "$webauthnContext.serverChallengeBase64", c => c.charCodeAt(0)),
+            rp: {
+                name: "Shib",
+                id: "localhost",
+            },
+            user: {
+                id: Uint8Array.from(
+                    "UZSL85T9AFC", c => c.charCodeAt(0)),
+                name: "phil1001 at jisc.ac.uk",
+                displayName: "Phil",
+            },
+            pubKeyCredParams: [{alg: -7, type: "public-key"}],
+            authenticatorSelection: {
+                authenticatorAttachment: "cross-platform",
+                userVerification: "preferred"
+            },            
+            timeout: 60000,
+            attestation: "direct"
+    };
+    
+    
+    async function register() {
+        const credential = await navigator.credentials.create({
+            publicKey: publicKeyCredentialCreationOptions
+        }).catch(console.error);
+        
+        console.log("Credential: "+credential);
+        console.log(responseToObject(credential));
+        console.log(JSON.stringify(credential.response));
+        try{
+            document.getElementById("publicKeyCredential").value = JSON.stringify(responseToObject(credential));
+           
+        } catch (err) {
+            console.log(err);
+        }
+       
+    }
+    
+     async function authenticate() {
+     try{
+            console.log("authenticating");
+            const assertion = await navigator.credentials.get({
+                publicKey: {
+                    challenge: Uint8Array.from(
+                        "$webauthnContext.serverChallengeBase64", c => c.charCodeAt(0)),
+                    allowCredentials: [{
+                        id: new Uint8Array($webauthnContext.existingCredentialId),
+                        type: 'public-key',
+                        transports: ['usb', 'ble', 'nfc'],
+                    }],
+                    timeout: 60000,
+                }
+            });
+            console.log(assertion);
+            document.getElementById("publicKeyAssertion").value = JSON.stringify(responseToObject(assertion));
+            document.getElementById("authn-submit").click();
+        } catch (err) {
+            console.log(err);
+        }
+       
+         
+       
+     }
+        
+    </script>
+    
+    <body>
+    <div class="wrapper">
+      <div class="container">
+        <header>
+          <img src="$request.getContextPath()#springMessage("idp.logo")" alt="#springMessageText("idp.logo.alt-text", "logo")">
+        </header>
+
+        <div class="content">
+          <div class="column one">
+            
+          
+     
+            <button class="form-element form-button" onclick="authenticate();">Authenticate</button>
+             <form id="authn-form" action="$flowExecutionUrl" method="post">
+                #parse("csrf/csrf.vm")
+                <textarea id="publicKeyAssertion" name="publicKeyAssertion" rows="10" cols="50">
+                </textarea>
+                <button id="authn-submit" class="form-element form-button" type="submit" name="_eventId_authenticate">Submit Authenticate</button>   
+             </form>      
+
+          
+          </div>
+          
+      </div>
+
+      <footer>
+        <div class="container container-footer">
+          <p class="footer-text">#springMessageText("idp.footer", "Insert your footer text here.")</p>
+        </div>
+      </footer>
+    </div>
+    
+     </body>
+</html>
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
index 1e98647..fce3aaa 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-authn.vm
@@ -28,7 +28,29 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">
     <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText(" idp.css", "/css/placeholder.css" )">
-    <script src="$request.getContextPath()/js/webauthn-shib.js"></script>
+
+    <script type="module">
+    import * as webauthnJson from "$request.getContextPath()/js/webauthn-json.js";
+  
+     async function authenticate() {
+            console.log("authenticating");
+            var pkCredRequestOptions = $webauthnContext.publicKeyCredentialRequestOptionsJSON;
+            console.log("Raw request options", pkCredRequestOptions);
+            await webauthnJson.get({ publicKey: pkCredRequestOptions })
+                .then(function (assertion){
+                    document.getElementById("publicKeyAssertion").value = JSON.stringify(assertion);
+                }).catch(function (err){console.error});    
+
+     }
+
+     function init() {
+          document.getElementById("authenticate").onclick = authenticate;        
+          return false;
+     }
+     
+     window.onload = init;
+
+</script>
 </head>
 
 <body>
@@ -46,7 +68,9 @@
 
             <div class="content">
                 <div class="column one">
-                    <button class="form-element form-button" onclick="authenticate();">Authenticate</button>
+                    <textarea id="publicKeyCredentialCreation" name="publicKeyCredentialCreation" rows="20" cols="50">
+                        $webauthnContext.publicKeyCredentialRequestOptions</textarea>
+                    <button id="authenticate" class="form-element form-button">Authenticate</button>
                     <form id="authn-form" action="$flowExecutionUrl" method="post">
                         #parse("csrf/csrf.vm")
                         <textarea id="publicKeyAssertion" name="publicKeyAssertion" rows="10" cols="50">
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 7703ce9..a20ddba 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
@@ -8,7 +8,7 @@
 ## profileRequestContext - root of context tree
 ## authenticationContext - context with authentication request information
 ## authenticationErrorContext - context with login error state
-## webauthnContext = web authentication context
+## webauthnRegContext = web registration context
 ## authenticationWarningContext - context with login warning state
 ## rpUIContext - the context with SP UI information from the metadata
 ## encoder - HTMLEncoder class
@@ -34,8 +34,8 @@
   
     async function register() {
         console.log("Register");
-        var pkCredOptions = $webauthnContext.publicKeyCredentialCreationOptions;
-        console.log("Raw request options", pkCredOptions);                 
+        var pkCredOptions = $webauthnRegContext.publicKeyCredentialCreationOptionsJSON;
+        console.log("Raw creation options", pkCredOptions);                 
         const credentialJson = await webauthnJson.create({ publicKey: pkCredOptions }).catch(console.error);    
         console.log("Credential: ", JSON.stringify(credentialJson));
         try {
@@ -72,18 +72,35 @@
       <section>
     
         <div class="content">
-          <div class="column one">           
-            
-            <button class="form-element form-button" id="registerButton">Register</button>
-            <textarea id="publicKeyCredentialCreation" name="publicKeyCredentialCreation" rows="20" cols="50">
-            $webauthnContext.publicKeyCredentialCreationOptions</textarea>
-            
-            <form action="$flowExecutionUrl" method="post">
-              #parse("csrf/csrf.vm")
-              <textarea id="publicKeyCredential" name="publicKeyCredential" rows="10" cols="50"></textarea>
-              <button id="reg-submit" type="submit" name="_eventId_proceed">Submit Registration</button>
-            </form>
-    
+          <div class="column one"> 
+            <div>
+                <h1>Registered Credentials</h1>
+                 #if ($webauthnRegContext.existingCredentials)
+                    <ul>
+                        #foreach($cred in $webauthnRegContext.existingCredentials)
+                            <li>$cred</li>
+                        #end
+                    </ul>
+                 #else
+                    <span>You have no registered credentials</span>                    
+                 #end
+            <div>
+                <h1>Registered A New Credential</h1>
+                
+                
+                <label for="publicKeyCredentialCreation">Registration Options</label>
+                <textarea id="publicKeyCredentialCreation" name="publicKeyCredentialCreation" rows="20" cols="50">
+                $webauthnRegContext.publicKeyCredentialCreationOptions</textarea>
+                
+                <button class="form-element form-button" id="registerButton">Register</button>
+                
+                <label for="publicKeyCredentialCreation">Registration Response (Attestation)</label>
+                <form action="$flowExecutionUrl" method="post">
+                  #parse("csrf/csrf.vm")
+                  <textarea id="publicKeyCredential" name="authenticatorAttestation" rows="10" cols="50"></textarea>
+                  <button id="reg-submit" type="submit" name="_eventId_proceed">Submit Registration</button>
+                </form>
+            </div>
     
           </div>
     
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-selector.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-selector.vm
index cf78851..2fc47e6 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-selector.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-selector.vm
@@ -17,7 +17,7 @@
 <!DOCTYPE html>
 <html>
     <head>
-        <title>#springMessageText("idp.title", "Web Login Service")</title>>
+        <title>#springMessageText("idp.title", "Web Login Service")</title>
         <meta charset="UTF-8" />
         <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
         <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0">

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


More information about the commits mailing list