[java-idp-plugin-webauthn] branch main updated: Update views. Improve flow and flow actions.

Phil Smart philip.smart at jisc.ac.uk
Fri Dec 8 17:23:09 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=6b13fef558a74b7c43b71b34a6ce84b033f8bac7

The following commit(s) were added to refs/heads/main by this push:
     new 6b13fef  Update views. Improve flow and flow actions.
6b13fef is described below

commit 6b13fef558a74b7c43b71b34a6ce84b033f8bac7
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Fri Dec 8 17:23:05 2023 +0000

    Update views. Improve flow and flow actions.
    
     - Add better storage records to display key information
     - Complete basic registration flow other than remove key
     - Add more key creation and authentication request options
---
 .../authn/webauthn/AbstractWebAuthnBaseAction.java |   9 +-
 .../AbstractWebAuthnRegistrationAction.java        |   9 +-
 .../webauthn/WebAuthnAuthenticationClient.java     |   5 +-
 .../webauthn/context/BaseWebAuthnContext.java      |  28 ++--
 .../context/WebAuthnRegistrationContext.java       |  54 +++----
 .../logic/IsDiscoverableCredentialRequired.java    |  61 ++++++++
 .../webauthn/storage}/CredentialRegistration.java  |  26 +++-
 .../StorageServiceCredentialRepository.java        |  32 ++++
 .../CreatePublicKeyCredentialCreationOptions.java  |  19 ++-
 ...actAuthenticatorAttestationFromFormRequest.java |  59 +++++--
 .../impl/PopulateWebAuthnRegistrationContext.java  |   2 +-
 .../admin/impl/StorePublicKeyCredential.java       |  29 ++--
 .../impl/YubicoWebauthnAuthenticationClient.java   |  23 ++-
 .../CreatePublicKeyCredentialRequestOptions.java   |  18 ++-
 .../impl/LookupRegisteredCredentials.java          |  21 +--
 .../PopulateWebAuthnAuthenticationContext.java     |  16 +-
 .../storage/impl/InMemoryRegistrationStorage.java  |   6 +-
 .../webauthn-registration-beans.xml                |   2 +-
 .../webauthn-registration-flow.xml                 |  36 +++--
 .../idp/flows/authn/WebAuthn/webauthn-beans.xml    |   7 +
 .../idp/flows/authn/WebAuthn/webauthn-flow.xml     |  25 ++-
 .../idp/plugin/authn/webauthn/css/webauthn.css     |  81 ++++++++++
 .../plugin/authn/webauthn/js/webauthn-support.js   |  16 ++
 .../idp/plugin/authn/webauthn/js/webauthn.js       | 171 +++++++++++++++++++++
 .../plugin/authn/webauthn/views/webauthn-authn.vm  |  27 ++--
 .../authn/webauthn/views/webauthn-register.vm      |  78 ++++++----
 .../authn/webauthn/views/webauthn-registered.vm    |  95 ++++++++----
 .../YubicoWebauthnAuthenticationClientTest.java    |  94 +++++------
 .../authn/webauthn/impl/MockAuthenticator.java     |  25 ++-
 .../impl/ValidatePublicKeyCredentialTest.java      |   1 +
 30 files changed, 815 insertions(+), 260 deletions(-)

diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
index a1034d1..92b2c34 100644
--- a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/AbstractWebAuthnBaseAction.java
@@ -27,10 +27,9 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-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.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;
@@ -70,7 +69,7 @@ public abstract class AbstractWebAuthnBaseAction extends AbstractProfileAction {
     
     /** The credential repository to store valid credentials in.*/
     // TODO replace with an adaptor to the storage service?
-    @NonnullAfterInit private CredentialRepository credentialRepository;
+    @NonnullAfterInit private StorageServiceCredentialRepository credentialRepository;
     
     
     /**
@@ -121,7 +120,7 @@ public abstract class AbstractWebAuthnBaseAction extends AbstractProfileAction {
      *  
      * @param repository The respository to set.
      */
-    public void setCredentialRepository(@Nonnull final CredentialRepository repository) {
+    public void setCredentialRepository(@Nonnull final StorageServiceCredentialRepository repository) {
         checkSetterPreconditions();
         credentialRepository = Constraint.isNotNull(repository, "Credential respository can not be null");
     }
@@ -131,7 +130,7 @@ public abstract class AbstractWebAuthnBaseAction extends AbstractProfileAction {
      * 
      * @return the credential repository.
      */
-    public CredentialRepository getCredentialRepository() {
+    public StorageServiceCredentialRepository getCredentialRepository() {
         return credentialRepository;
     }
     
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 3eb3f2e..e3981c5 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,8 @@ import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
-import com.yubico.webauthn.CredentialRepository;
-
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+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;
@@ -68,7 +67,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
     
     /** The credential respository to store valid credentials in.*/
     // TODO replace with an adaptor to the storage service?
-    @NonnullAfterInit private CredentialRepository credentialRepository;
+    @NonnullAfterInit private StorageServiceCredentialRepository credentialRepository;
     
     
     /**
@@ -118,7 +117,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
      *  
      * @param repository The respository to set.
      */
-    public void setCredentialRepository(@Nonnull final CredentialRepository repository) {
+    public void setCredentialRepository(@Nonnull final StorageServiceCredentialRepository repository) {
         checkSetterPreconditions();
         credentialRepository = Constraint.isNotNull(repository, "Credential respository can not be null");
     }
@@ -128,7 +127,7 @@ public abstract class AbstractWebAuthnRegistrationAction extends AbstractProfile
      * 
      * @return the credential repository.
      */
-    public CredentialRepository getCredentialRepository() {
+    public StorageServiceCredentialRepository getCredentialRepository() {
         return credentialRepository;
     }
     
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 d44024d..35ffa11 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,5 +1,6 @@
 package net.shibboleth.idp.plugin.authn.webauthn;
 
+import java.util.List;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
@@ -45,7 +46,7 @@ public interface WebAuthnAuthenticationClient {
       *         
       */
      @Nonnull PublicKeyCredentialRequestOptions createAuthenticationRequest(@Nullable final String username, 
-             @Nullable final Set<PublicKeyCredentialDescriptor> allowCredentials, @Nonnull final byte[] challenge) 
+             @Nonnull final List<PublicKeyCredentialDescriptor> allowCredentials, @Nonnull final byte[] challenge) 
                      throws WebAuthnAuthenticationClientException;
      
      /**
@@ -63,7 +64,7 @@ public interface WebAuthnAuthenticationClient {
       * @throws WebAuthnAuthenticationClientException if there is an error generating the creation request
       */
      @Nonnull PublicKeyCredentialCreationOptions createRegistrationRequest(
-             @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, @Nullable final String username, 
+             @Nonnull final Set<PublicKeyCredentialDescriptor> excludeCredentials, @Nullable final String username, 
              @Nullable final byte[] userHandle, @Nonnull final byte[] challenge) 
                      throws WebAuthnAuthenticationClientException;
      
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 3632834..4fdb824 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,17 +14,19 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.context;
 
-import java.util.Set;
+import java.util.Collection;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.BaseContext;
 
-import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
-
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
 import net.shibboleth.shared.codec.Base64Support;
 import net.shibboleth.shared.codec.EncodingException;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
@@ -32,14 +34,17 @@ import net.shibboleth.shared.logic.Constraint;
  */
 public class BaseWebAuthnContext extends BaseContext {
     
-    /** The original username. */
+    /** 
+     * The username of the user that is the subject of this authentication. If {@code null} we can not determine 
+     * the userHandle (and hence public key) to use, and the flow will require a discoverable credential.
+     */
     @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;
+    @Nullable private Collection<CredentialRegistration> existingCredentials;
     
     /** The challenge sent to the authenticator in both registration and authentication ceremonies.*/
     @Nullable private byte[] serverChallenge;  
@@ -57,7 +62,7 @@ public class BaseWebAuthnContext extends BaseContext {
     }
 
     /**
-     * Sets the username and resets the transformed version to be identical.
+     * Sets the username.
      * 
      * @param name the username
      * 
@@ -65,7 +70,6 @@ public class BaseWebAuthnContext extends BaseContext {
      */
     @Nonnull public BaseWebAuthnContext setUsername(@Nullable final String name) {
         username = name;
-        //transformedUsername = name;
         return this;
     }
 
@@ -74,7 +78,7 @@ public class BaseWebAuthnContext extends BaseContext {
      * 
      * @param credentials the set of credentials
      */
-    public void setExistingCredentials(@Nullable final Set<PublicKeyCredentialDescriptor> credentials) {
+    public void setExistingCredentials(@Nullable final Collection<CredentialRegistration> credentials) {
         existingCredentials = credentials;
         
     }
@@ -84,8 +88,12 @@ public class BaseWebAuthnContext extends BaseContext {
      * 
      * @return the excluded credentials.
      */
-    public Set<PublicKeyCredentialDescriptor> getExistingCredentials() {
-        return existingCredentials;
+    @Nonnull @Unmodifiable @NotLive public Collection<CredentialRegistration> getExistingCredentials() {
+        final Collection<CredentialRegistration> localExistingCredentials = existingCredentials;
+        if (localExistingCredentials == null) {
+            return CollectionSupport.emptyList();
+        }
+        return CollectionSupport.copyToList(localExistingCredentials);
     }
     
     /**
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 e7d0cda..1ef8de2 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
@@ -1,6 +1,5 @@
 package net.shibboleth.idp.plugin.authn.webauthn.context;
 
-import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.NotThreadSafe;
 
@@ -11,7 +10,10 @@ import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 
 
-/** Registration context for processing WebAuthn Registration Ceremonies. */
+/** 
+ * Registration context for processing WebAuthn Registration Ceremonies. This context is intended for registration of
+ * a single credential only. A context would be needed per credential registration.
+ */
 @NotThreadSafe
 public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
 
@@ -42,6 +44,8 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
      */
     @Nullable private RegistrationResult registrationResult;
     
+    /** A display friendly nickname for the credential that is to be registered.*/
+    @Nullable private String credentialNickname;    
 
     /**
      * Get the attestation response as a result of creating a new credential.
@@ -65,33 +69,6 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
         return this;
     }
     
-    /**
-     * Gets the username.
-     * 
-     * @return the username
-     */
-    @Override
-    @Nullable public String getUsername() {
-        return username;
-    }
-
-    /**
-     * Sets the username and resets the transformed version to be identical.
-     * 
-     * @param name the username
-     * 
-     * @return this context
-     */
-    @Override
-    @Nonnull public WebAuthnRegistrationContext setUsername(@Nullable final String name) {
-        username = name;
-        //transformedUsername = name;
-        return this;
-    }
-    
-    
-
-    
     /**
      * Set the options used to create public key credentials. 
      * 
@@ -148,5 +125,24 @@ public final class WebAuthnRegistrationContext extends BaseWebAuthnContext {
         return this;
     }
 
+    
+    /**
+     * Set the display friendly nickname for this credential.
+     * 
+     * @param nickname The credential nickname to set.
+     */
+    public WebAuthnRegistrationContext setCredentialNickname(@Nullable final String nickname) {
+        credentialNickname = nickname;
+        return this;
+    }
+    
+    /**
+     * Get the display friendly nickname for this credential.
+     * 
+     * @return the credential nickname.
+     */
+    public String getCredentialNickname() {
+        return credentialNickname;
+    }
 
 }
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsDiscoverableCredentialRequired.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsDiscoverableCredentialRequired.java
new file mode 100644
index 0000000..3db25ca
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/context/logic/IsDiscoverableCredentialRequired.java
@@ -0,0 +1,61 @@
+/*
+ * 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.context.logic;
+
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.WebAuthnAuthenticationContext;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/** 
+ * A predicate that determines if the authentication ceremony requires a discoverable credential supplied by the 
+ * authenticator, or if we have a username to determine which credentials to use on from the IdP. 
+ */
+public class IsDiscoverableCredentialRequired implements Predicate<ProfileRequestContext> {
+    
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(IsDiscoverableCredentialRequired.class);
+
+    @Override
+    public boolean test(@Nullable final ProfileRequestContext input) {
+        if (input == null) {
+            log.trace("Profile context was null, can not determine if discoverable credentials are required");
+            return false;
+        }
+        final AuthenticationContext authnContext = input.getSubcontext(AuthenticationContext.class);
+        if (authnContext == null) {
+            log.trace("Authentication context was null, can not determine if discoverable credentials are required");
+            return false;
+        }
+        final WebAuthnAuthenticationContext webauthnContext = 
+                authnContext.getSubcontext(WebAuthnAuthenticationContext.class);
+        if (webauthnContext == null) {
+            log.trace("WebAuthn authentication context was null, can not determine if discoverable credentials "
+                    + "are required");
+            return false;
+        }
+        final boolean discoverableCredentialRequired = webauthnContext.getUsername() == null;
+        log.debug("{}", discoverableCredentialRequired ? "Usernameless authentication required" : 
+            "Passwordless authentication required for '"+webauthnContext.getUsername()+"'");
+        return discoverableCredentialRequired;
+    }
+}
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
similarity index 78%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java
rename to webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
index 1a0a0cc..cc8368d 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/CredentialRegistration.java
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/CredentialRegistration.java
@@ -15,32 +15,39 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.plugin.authn.webauthn.storage.impl;
+package net.shibboleth.idp.plugin.authn.webauthn.storage;
 
 import java.time.Instant;
 import java.util.Optional;
 import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import com.yubico.webauthn.RegisteredCredential;
 import com.yubico.webauthn.data.AuthenticatorTransport;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
 import com.yubico.webauthn.data.UserIdentity;
 
 /**
  * Influenced by the CredentialRegistration class in the Yubico demo libraries.
  */
 //TODO need our own storage record, so this should be test only and then replaced with the actual one eventually
+//TODO make this more official and undeprecate
+ at Deprecated
 public class CredentialRegistration {
 
     UserIdentity userIdentity;
     Optional<String> credentialNickname;
-    SortedSet<AuthenticatorTransport> transports;
+    @Nonnull SortedSet<AuthenticatorTransport> transports;
     Instant registrationTime;
     RegisteredCredential credential;
     Optional<Object> attestationMetadata;
     
     
     public CredentialRegistration(final UserIdentity userIdentity, final Optional<String> credentialNickname,
-            final SortedSet<AuthenticatorTransport> transports, final Instant registrationTime, 
+            @Nonnull final SortedSet<AuthenticatorTransport> transports, final Instant registrationTime, 
             final RegisteredCredential credential,
             final Optional<Object> attestationMetadata) {
         super();
@@ -52,8 +59,12 @@ public class CredentialRegistration {
         this.attestationMetadata = attestationMetadata;
     }
     
+    public String getNickname(){
+        return credentialNickname.orElse("");
+    }
+    
     public CredentialRegistration() {
-        
+        transports = new TreeSet<AuthenticatorTransport>();
     }
 
     public String getRegistrationTimestamp() {
@@ -76,6 +87,13 @@ public class CredentialRegistration {
         return transports;
     }
     
+    @Nullable public PublicKeyCredentialDescriptor toPublicKeyCredentialDescriptor() {
+        return  PublicKeyCredentialDescriptor.builder()
+                .id(credential.getCredentialId())
+                .transports(transports)
+                .build();
+    }
+    
     public CredentialRegistration withCredential(final RegisteredCredential newRegCred) {
         final CredentialRegistration newReg = new CredentialRegistration();
         newReg.attestationMetadata = attestationMetadata;
diff --git a/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/StorageServiceCredentialRepository.java b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/StorageServiceCredentialRepository.java
new file mode 100644
index 0000000..69c01c9
--- /dev/null
+++ b/webauthn-api/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/StorageServiceCredentialRepository.java
@@ -0,0 +1,32 @@
+/*
+ * 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.storage;
+
+import java.util.Collection;
+
+import com.yubico.webauthn.CredentialRepository;
+
+
+/**
+ * An IdP extension of the Yubico {@link CredentialRepository} interface to support additional operations required
+ * by the IdP.  
+ */
+public interface StorageServiceCredentialRepository extends CredentialRepository {
+    
+    Collection<CredentialRegistration> getRegistrationsByUsername(final String username);
+    
+    boolean addRegistrationByUsername(final String username, final CredentialRegistration reg);
+
+}
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 e97ba8c..304d02b 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
@@ -15,6 +15,11 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+
 import javax.annotation.Nonnull;
 
 import org.opensaml.profile.action.ActionSupport;
@@ -24,12 +29,14 @@ import org.slf4j.Logger;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnRegistrationAction;
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
@@ -81,12 +88,18 @@ public class CreatePublicKeyCredentialCreationOptions extends AbstractWebAuthnRe
             return;
         }
         
-        try {                
+        try {         
+            final Collection<CredentialRegistration> existingCredentials = context.getExistingCredentials();
+            final Set<PublicKeyCredentialDescriptor> existingCredentialDescriptors  = existingCredentials.stream()
+                .map(cred -> cred.toPublicKeyCredentialDescriptor())
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+            
             final PublicKeyCredentialCreationOptions pkCredCreationOptions = 
-                    client.createRegistrationRequest(context.getExistingCredentials(),context.getUsername(), 
+                    client.createRegistrationRequest(existingCredentialDescriptors, context.getUsername(), 
                             context.getUserHandle(), challenge);
             context.setPublicKeyCredentialCreationOptions(pkCredCreationOptions);
-            //convert to JSON
+            //convert to JSON for the JS api to use
             context.setPublicKeyCredentialCreationOptionsJSON(
                     objectMapper.writeValueAsString(pkCredCreationOptions));
             
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 dae40fe..5956c7d 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
@@ -48,21 +48,28 @@ import net.shibboleth.shared.primitive.StringSupport;
  */
 public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebAuthnRegistrationAction {
 
-    /** Default token code field name. */
-    @Nonnull @NotEmpty public static final String DEFAULT_FIELD_NAME = "authenticatorAttestation";
+    /** Default assertion field name. */
+    @Nonnull @NotEmpty public static final String DEFAULT_ASSERTION_FIELD_NAME = "authenticatorAttestation";
+    
+    /** Default nickname field name. */
+    @Nonnull @NotEmpty public static final String DEFAULT_NICKNAME_FIELD_NAME = "credentialNickname";
     
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(ExtractAuthenticatorAttestationFromFormRequest.class);
     
-    /** Name of header. */
-    @Nonnull @NotEmpty private String fieldName;
+    /** Name of assertion field. */
+    @Nonnull @NotEmpty private String assertionFieldName;
+    
+    /** Name of nickname field. */
+    @Nonnull @NotEmpty private  String credentialNicknameFieldName;
     
     /** JSON object mapper. */
     @NonnullAfterInit private ObjectMapper objectMapper;
     
     /** Constructor. */
     public ExtractAuthenticatorAttestationFromFormRequest() {
-        fieldName = DEFAULT_FIELD_NAME;
+        assertionFieldName = DEFAULT_ASSERTION_FIELD_NAME;
+        credentialNicknameFieldName = DEFAULT_NICKNAME_FIELD_NAME;
     }
     
     @Override protected void doInitialize() throws ComponentInitializationException {
@@ -85,16 +92,28 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
     }
     
     /**
-     * Set the name of the field to examine.
+     * Set the name of the assertion field to examine.
      * 
      * @param field field name
      */
-    public void setFieldName(@Nonnull @NotEmpty final String field) {
+    public void setAssertionFieldName(@Nonnull @NotEmpty final String field) {
         checkSetterPreconditions();
         
-        fieldName = Constraint.isNotNull(StringSupport.trimOrNull(field), "Field name cannot be null or empty");
+        assertionFieldName = Constraint.isNotNull(StringSupport.trimOrNull(field), 
+                "Assertion Field name cannot be null or empty");
     }
     
+    /**
+     * Set the name of the nickname field to examine.
+     * 
+     * @param field field name
+     */
+    public void setCredentialNicknameFieldName(@Nonnull @NotEmpty final String field) {
+        checkSetterPreconditions();
+        
+        credentialNicknameFieldName = Constraint.isNotNull(StringSupport.trimOrNull(field),
+                "Nickname FieldName can not be null");
+    }
     
     @Override
     protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
@@ -107,10 +126,18 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
             return;
         }
         
-        final String pkCredAttestationJson = extractPublicKeyCredential(request);   
-        log.trace("Public Key Credential AuthenticatorAttestationResponse in JSON is '{}'",pkCredAttestationJson);
+        final String pkCredAttestationJson = extractParameter(request, assertionFieldName);   
+        log.trace("Public key credential authenticator attestation response in JSON is '{}'",pkCredAttestationJson);
         if (StringSupport.trimOrNull(pkCredAttestationJson) == null) {
-            log.warn("{} Could not extract AuthenticatorAttestationResponse from form", getLogPrefix());
+            log.warn("{} Could not extract authenticator attestation response from form", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
+            return;
+        }
+        
+        final String credNickname = extractParameter(request, credentialNicknameFieldName);   
+        log.trace("Public key credential nickname is '{}'",pkCredAttestationJson);
+        if (StringSupport.trimOrNull(credNickname) == null) {
+            log.warn("{} Could not extract nickname from form", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
             return;
         }
@@ -119,6 +146,7 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
             final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> 
                 pkCredAttestation = PublicKeyCredential.parseRegistrationResponseJson(pkCredAttestationJson);
             context.setAuthenticatorAttestationResponse(pkCredAttestation);
+            context.setCredentialNickname(credNickname);
         } catch (final IOException e) {
             log.warn("{} Could not convert AuthenticatorAttestationResponse from form", getLogPrefix(), e);
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.NO_CREDENTIALS);
@@ -130,14 +158,15 @@ public class ExtractAuthenticatorAttestationFromFormRequest extends AbstractWebA
     }
 
     /**
-     * Extract the public key credential from the AuthenticatorAttestationResponse in the form.
+     * Extract the given parameter from the servlet request.
      * 
      * @param httpRequest the http request
      * 
-     * @return the AuthenticationAttestationResponse JSON.
+     * @return the value of the parameter.
      */
-    @Nullable private String extractPublicKeyCredential(@Nonnull final HttpServletRequest httpRequest) {
-        return httpRequest.getParameter(fieldName);
+    @Nullable private String extractParameter(@Nonnull final HttpServletRequest httpRequest, 
+            @Nonnull @NotEmpty final String field) {
+        return httpRequest.getParameter(field);
     }
     
 }
\ No newline at end of file
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java
index 588a3b1..a201063 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/PopulateWebAuthnRegistrationContext.java
@@ -106,7 +106,7 @@ public class PopulateWebAuthnRegistrationContext extends AbstractProfileAction {
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         }
-        context.setUsername(usernameLookupStrategy.apply(profileRequestContext));
+        context.setUsername(username);
 
         log.debug("Created WebAuthn registration context for user '{}'", context.getUsername());
     }
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 2c1c36c..df35521 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
@@ -19,6 +19,7 @@ package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
 
 import java.time.Instant;
 import java.util.Optional;
+import java.util.SortedSet;
 import java.util.TreeSet;
 
 import javax.annotation.Nonnull;
@@ -41,8 +42,7 @@ import net.shibboleth.idp.authn.AuthnEventIds;
 import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnRegistrationAction;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnPublicKeyCredentialRecord;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.CredentialRegistration;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.WebauthnPublicKeyCredentialStorageSerializer;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.component.ComponentInitializationException;
@@ -108,7 +108,7 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
         }        
         final RegistrationResult registrationResult = context.getRegistrationResult();
         if (registrationResult == null) {
-            log.error("Unable to find registration information in registration context");
+            log.error("Unable to find registration result in registration context");
             ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_AUTHN_CTX);
             return;
         } 
@@ -126,19 +126,18 @@ public class StorePublicKeyCredential extends AbstractWebAuthnRegistrationAction
                     .build();
             
             // TODO fixup the record we will use to store registrations
-            final CredentialRegistration registration = new CredentialRegistration(user, Optional.of("Nickname"), 
-                    new TreeSet<AuthenticatorTransport>(), Instant.now(), credential, Optional.empty());
+            final SortedSet<AuthenticatorTransport> transports = 
+                    registrationResult.getKeyId().getTransports().orElse(new TreeSet<>());
             
-            // TODO should not need a cast here when we sort out the storage
-            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());
-            } else {
-                log.debug("{} Unsupported credential repository type", getLogPrefix());
-                ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
-                return;
-            }
+            final CredentialRegistration registration = new CredentialRegistration(user, 
+                    Optional.of(context.getCredentialNickname()), 
+                    transports, Instant.now(), credential, Optional.empty());
+            
+
+            getCredentialRepository().addRegistrationByUsername(username, registration);
+            log.debug("{} Added public key credential registration for user '{}' and key '{}' ", 
+                    getLogPrefix(), username, registrationResult.getKeyId().getId().getBase64Url());
+
         } catch (final Exception e) {
             log.error("{} Unable to store registration for key '{}'",getLogPrefix(), 
                     registrationResult.getKeyId().getId().getBase64Url(), e);
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 fd2f271..f651a4f 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,7 +14,6 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.client.impl;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -101,7 +100,7 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
 
     @Override
     public PublicKeyCredentialRequestOptions createAuthenticationRequest(@Nullable final String username, 
-            @Nullable final Set<PublicKeyCredentialDescriptor> allowCredentials, final byte[] challenge) 
+            @Nullable final List<PublicKeyCredentialDescriptor> allowCredentials, @Nonnull final byte[] challenge) 
                     throws WebAuthnAuthenticationClientException {
  
         //set default to preferred.
@@ -109,16 +108,12 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
         if (username == null) {
             //then require user verification? makes sense, but is that part of the spec?
             userVerificationRequirement = UserVerificationRequirement.REQUIRED;
-        }
-        
-        // 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(Optional.ofNullable(listOfCredentials))
+                    .allowCredentials(Optional.ofNullable(allowCredentials))
 //                        .extensions(
 //                            startAssertionOptions
 //                                .getExtensions()
@@ -136,16 +131,20 @@ public class YubicoWebauthnAuthenticationClient implements WebAuthnAuthenticatio
     /** {@inheritDoc} */
     @Override
     public PublicKeyCredentialCreationOptions createRegistrationRequest(
-            @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, final String username, 
+            @Nullable final Set<PublicKeyCredentialDescriptor> excludeCredentials, @Nullable final String username, 
             final byte[] userHandle, final byte[] challenge) throws WebAuthnAuthenticationClientException {
        
         //set default to preferred.
-        UserVerificationRequirement userVerificationRequirement = UserVerificationRequirement.PREFERRED;
+        ResidentKeyRequirement residentKeyRquirement = ResidentKeyRequirement.PREFERRED;        
         if (username == null) {
             //then require user verification? makes sense, but is that part of the spec?
-            userVerificationRequirement = UserVerificationRequirement.REQUIRED;
+            residentKeyRquirement = ResidentKeyRequirement.REQUIRED;
+
         }
-        final ResidentKeyRequirement residentKeyRquirement = ResidentKeyRequirement.REQUIRED;
+        
+        // If U2F we do not need UV. So conditional required.
+        final UserVerificationRequirement userVerificationRequirement = UserVerificationRequirement.PREFERRED;
+        
         final UserIdentity identity = 
                 UserIdentity.builder().name(username).displayName(username).id(new ByteArray(userHandle)).build();
         
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 2a07e07..e1ba200 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
@@ -15,6 +15,10 @@
 
 package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
+
 import javax.annotation.Nonnull;
 
 import org.opensaml.profile.action.ActionSupport;
@@ -23,6 +27,7 @@ import org.slf4j.Logger;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
 import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 
 import net.shibboleth.idp.authn.AuthnEventIds;
@@ -31,6 +36,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.AbstractWebAuthnAuthenticationAc
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClient;
 import net.shibboleth.idp.plugin.authn.webauthn.WebAuthnAuthenticationClientException;
 import net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
@@ -84,11 +90,17 @@ public class CreatePublicKeyCredentialRequestOptions extends AbstractWebAuthnAut
         }
                 
         try {
-            //TODO userhandle needs to be pulled out.
+            final Collection<CredentialRegistration> existingCredentials = context.getExistingCredentials();
+            final List<PublicKeyCredentialDescriptor> existingCredentialDescriptors  = existingCredentials.stream()
+                .map(cred -> cred.toPublicKeyCredentialDescriptor())
+                .filter(Objects::nonNull)
+                .toList();
+            
             final PublicKeyCredentialRequestOptions pkCredRequestOptions = 
-                    client.createAuthenticationRequest(context.getUsername(), null, challenge);
+                    client.createAuthenticationRequest(context.getUsername(), existingCredentialDescriptors, 
+                            challenge);
             context.setPublicKeyCredentialRequestOptions(pkCredRequestOptions);
-            
+            // Convert to JSON for the view. TODO maybe that should be converted by velocity
             context.setPublicKeyCredentialRequestOptionsJSON(objectMapper.writeValueAsString(pkCredRequestOptions));
             
             log.debug("{} Created PublicKeyCredentialRequestOptions: '{}'",getLogPrefix(), pkCredRequestOptions);
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/impl/LookupRegisteredCredentials.java
similarity index 79%
rename from webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/admin/impl/LookupRegisteredCredentials.java
rename to webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/impl/LookupRegisteredCredentials.java
index 35d8c1a..926dc72 100644
--- 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/impl/LookupRegisteredCredentials.java
@@ -12,9 +12,9 @@
  * limitations under the License.
  */
 
-package net.shibboleth.idp.plugin.authn.webauthn.admin.impl;
+package net.shibboleth.idp.plugin.authn.webauthn.impl;
 
-import java.util.Set;
+import java.util.Collection;
 
 import javax.annotation.Nonnull;
 
@@ -22,21 +22,21 @@ 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.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
- * Action that lookups existing registered credentials
+ * Action that lookups existing registered credentials.
  */
 public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
-    
+
     /** Class logger. */
-    @Nonnull private final Logger log = LoggerFactory.getLogger(LookupRegisteredCredentials.class);
-    
+    @Nonnull
+    private final Logger log = LoggerFactory.getLogger(LookupRegisteredCredentials.class);
+
     /** {@inheritDoc} */
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext,
             @Nonnull final BaseWebAuthnContext context) {
@@ -49,8 +49,9 @@ public class LookupRegisteredCredentials extends AbstractWebAuthnBaseAction {
             return;
         } 
         
-        final Set<PublicKeyCredentialDescriptor> credentials =
-                getCredentialRepository().getCredentialIdsForUsername(username);       
+        final Collection<CredentialRegistration> credentials =
+                getCredentialRepository().getRegistrationsByUsername(username);   
+        
 
         log.debug("{} Found '{}' registered credentials for '{}'", getLogPrefix(), 
                 credentials != null ? credentials.size() : "0", username);
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 79bb7e3..17d5f89 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
@@ -88,7 +88,7 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
     }
     
     /**
-     * Set the lookup strategy to use for the username to match against Duo identity.
+     * Set the lookup strategy to use for the username to use if we are not using a discoverable credential.
      * 
      * @param strategy lookup strategy
      */
@@ -112,15 +112,21 @@ public class PopulateWebAuthnAuthenticationContext extends AbstractAuthenticatio
             return;
         }
         
-        final String username = usernameLookupStrategy.apply(profileRequestContext);
+        String username = usernameLookupStrategy.apply(profileRequestContext);
+        //FIXME: testing
+        username = "philsmart";
         if (username == null && usernameRequiredPredicate.test(profileRequestContext)) {
             log.error("{} Error creating WebauthnAuthenticationContext, no username found", getLogPrefix());
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
             return;
         }
-        context.setUsername(username);
-
-        log.debug("Created Webauthn authentication context");
+        if (username != null) {
+            context.setUsername(username);
+            log.debug("Created Webauthn authentication context for user '{}'", context.getUsername());
+            return;
+        }
+        log.debug("Created Webauthn authentication context for a discoverable credential (no username)'");
+        
     }
     
     
diff --git a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
index aa58bec..fe7c09c 100644
--- a/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
+++ b/webauthn-impl/src/main/java/net/shibboleth/idp/plugin/authn/webauthn/storage/impl/InMemoryRegistrationStorage.java
@@ -40,13 +40,15 @@ import org.slf4j.LoggerFactory;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.yubico.webauthn.AssertionResult;
-import com.yubico.webauthn.CredentialRepository;
 import com.yubico.webauthn.RegisteredCredential;
 import com.yubico.webauthn.data.ByteArray;
 import com.yubico.webauthn.data.PublicKeyCredentialDescriptor;
 
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.StorageServiceCredentialRepository;
 
-public class InMemoryRegistrationStorage implements CredentialRepository {
+
+public class InMemoryRegistrationStorage implements StorageServiceCredentialRepository {
 
   private final Cache<String, Set<CredentialRegistration>> storage =
       CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(1, TimeUnit.DAYS).build();
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 da08c57..75bc9df 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
@@ -26,7 +26,7 @@
         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"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
         p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnRegistrationContext"/>
 
     <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
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 983a7cf..b85d4b3 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
@@ -46,6 +46,7 @@
             <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
             <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
         </on-render>
+        
        <transition on="proceed" to="ExtractPublicKeyCredential" />
        
     </view-state>
@@ -59,20 +60,27 @@
         <transition on="proceed" to="DisplayWebAuthnSuccessfulRegistration" />
     </action-state>
     
-    <end-state id="DisplayWebAuthnSuccessfulRegistration" view="webauthn/webauthn-registered">
-          <on-entry>
-                <evaluate expression="environment" result="requestScope.environment" />
-                <evaluate expression="opensamlProfileRequestContext" result="requestScope.profileRequestContext" />
-                <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="requestScope.authenticationContext" />
-                <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext)).getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext))" result="requestScope.webauthnContext" />
-                <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.ui.context.RelyingPartyUIContext))" result="requestScope.rpUIContext" />
-                <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationErrorContext))" result="requestScope.authenticationErrorContext" />
-                <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationWarningContext))" result="requestScope.authenticationWarningContext" />
-                <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="requestScope.encoder" />
-                <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="requestScope.request" />
-                <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="requestScope.response" />
-            </on-entry>
-     </end-state>
+    <view-state id="DisplayWebAuthnSuccessfulRegistration" view="webauthn/webauthn-registered">
+        <on-entry>
+            <evaluate expression="LookupRegisteredCredentials"/>
+        </on-entry>
+        <on-render>
+            <evaluate expression="environment" result="viewScope.environment" />
+            <evaluate expression="opensamlProfileRequestContext" result="viewScope.profileRequestContext" />
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationContext))" result="viewScope.authenticationContext" />
+            <evaluate expression="opensamlProfileRequestContext.getSubcontext(T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnRegistrationContext))" result="viewScope.webauthnRegContext" />
+            <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.ui.context.RelyingPartyUIContext))" result="viewScope.rpUIContext" />
+            <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationErrorContext))" result="viewScope.authenticationErrorContext" />
+            <evaluate expression="authenticationContext.getSubcontext(T(net.shibboleth.idp.authn.context.AuthenticationWarningContext))" result="viewScope.authenticationWarningContext" />
+            <evaluate expression="T(net.shibboleth.utilities.java.support.codec.HTMLEncoder)" result="viewScope.encoder" />
+            <evaluate expression="flowRequestContext.getExternalContext().getNativeRequest()" result="viewScope.request" />
+            <evaluate expression="flowRequestContext.getExternalContext().getNativeResponse()" result="viewScope.response" />
+        </on-render>
+            
+        <transition on="proceed" to="RegistrationComplete" />
+     </view-state>
+     
+     <end-state id="RegistrationComplete"/>
     
     <bean-import resource="webauthn-registration-beans.xml" />
     <bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/authn/webauthn/webauthn-abstract-beans.xml" />
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 6f58664..5d3ac72 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
@@ -14,6 +14,9 @@
         p:usernameRequiredPredicate="false">
     </bean>
     
+     <bean id="IsDiscoverableCredentialRequired" scope="prototype"
+        class="net.shibboleth.idp.plugin.authn.webauthn.context.logic.IsDiscoverableCredentialRequired"/>
+    
     <bean id="shibboleth.ChildLookup.WebAuthnAuthenticationContext"
         class="org.opensaml.messaging.context.navigate.ChildContextLookup"
         c:type="#{ T(net.shibboleth.idp.plugin.authn.webauthn.context.WebAuthnAuthenticationContext) }" />
@@ -21,6 +24,10 @@
     <bean id ="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"
             parent="shibboleth.Functions.Compose" c:f-ref="shibboleth.ChildLookup.AuthenticationContext"
             c:g-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContext" />
+            
+    <bean id="LookupRegisteredCredentials" parent="AbstractWebAuthnRegistrationAction"
+        class="net.shibboleth.idp.plugin.authn.webauthn.impl.LookupRegisteredCredentials"
+        p:webAuthnBaseContextLookupStrategy-ref="shibboleth.ChildLookup.WebAuthnAuthenticationContextFromAuthenticationContext"/>
 
     <bean id="GenerateServerChallenge" parent="AbstractWebAuthnBaseAction"
         class="net.shibboleth.idp.plugin.authn.webauthn.impl.GenerateServerChallenge"
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 0eac3c0..a4837d2 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
@@ -4,14 +4,35 @@
 
     <action-state id="PopulateWebauthnContext">
         <evaluate expression="PopulateWebAuthnAuthenticationContext"/>
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="DetermineUsernamelessOrPasswordlessLogin" />    
+    </action-state>
+    
+    <decision-state id="DetermineUsernamelessOrPasswordlessLogin">
+        <if test="IsDiscoverableCredentialRequired.test(opensamlProfileRequestContext)"
+            then="UsernamelessLogin" 
+            else="PasswordlessLogin" />        
+    </decision-state>
+    
+    <!-- If passwordless and not usernameless we need a different flow here. Also User verification not required for 2FA-->
+    <action-state id="UsernamelessLogin">
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="GenerateRegistrationCeremonyOptions" />    
+    </action-state>
+    
+    <action-state id="PasswordlessLogin">
+        <evaluate expression="LookupRegisteredCredentials"/>
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="GenerateRegistrationCeremonyOptions" />    
+    </action-state>
+    
+     <action-state id="GenerateRegistrationCeremonyOptions">
         <evaluate expression="GenerateServerChallenge"/>
         <evaluate expression="CreatePublicKeyCredentialRequestOptions"/>
         <evaluate expression="'proceed'" />
         <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/css/webauthn.css b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
new file mode 100644
index 0000000..b9d5cf5
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/css/webauthn.css
@@ -0,0 +1,81 @@
+ at charset "UTF-8";
+
+table {
+    border-collapse: collapse;
+    width: 100%;
+}
+
+th,
+td {
+    padding: 8px;
+    text-align: left;
+    border-bottom: 1px solid #ddd;
+}
+
+tr:hover {
+    background-color: coral;
+}
+
+
+/* Style the button that is used to open and close the collapsible content */
+.collapsible {
+    background-color: #eee;
+    color: #444;
+    cursor: pointer;
+    padding: 18px;
+    width: 100%;
+    border: none;
+    text-align: left;
+    outline: none;
+    font-size: 15px;
+}
+
+/* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
+.active,
+.collapsible:hover {
+    background-color: #ccc;
+}
+
+/* Style the collapsible content. Note: hidden by default */
+.debug {
+    padding: 0 18px;
+    display: none;
+    overflow: hidden;
+    background-color: #f1f1f1;
+}
+
+.collapsible:after {
+    content: '\02795';
+    /* Unicode character for "plus" sign (+) */
+    font-size: 13px;
+    color: white;
+    float: right;
+    margin-left: 5px;
+}
+
+.active:after {
+    content: "\2796";
+    /* Unicode character for "minus" sign (-) */
+}
+
+.hidden {
+    display: none;
+}
+
+.centre {
+    margin-left: auto;
+    margin-right: auto;
+    text-align: center;
+}
+
+.webauthn-table-button {
+    background-color: rgb(221, 5, 0);
+    /* Green */
+    border: none;
+    color: white;
+    padding: 10px 25px;
+    text-align: center;
+    text-decoration: none;
+    display: inline-block;
+    font-size: 16px;
+}
\ No newline at end of file
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js
new file mode 100644
index 0000000..006d9c9
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn-support.js
@@ -0,0 +1,16 @@
+document.addEventListener('DOMContentLoaded', function() {
+   var coll = document.getElementsByClassName("collapsible");
+    var i;
+
+    for (i = 0; i < coll.length; i++) {
+        coll[i].addEventListener("click", function() {
+            this.classList.toggle("active");
+            var content = this.nextElementSibling;
+            if (content.style.display === "block") {
+                content.style.display = "none";
+            } else {
+                content.style.display = "block";
+            }
+        });
+    }
+});
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn.js b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn.js
new file mode 100644
index 0000000..2cf5335
--- /dev/null
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/js/webauthn.js
@@ -0,0 +1,171 @@
+// Copyright (c) 2018, Yubico AB
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+(function(root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define(['base64url'], factory);
+  } else if (typeof module === 'object' && module.exports) {
+    module.exports = factory(require('base64url'));
+  } else {
+    root.webauthn = factory(root.base64url);
+  }
+})(this, function(base64url) {
+
+  function extend(obj, more) {
+    return Object.assign({}, obj, more);
+  }
+
+  /**
+   * Create a WebAuthn credential.
+   *
+   * @param request: object - A PublicKeyCredentialCreationOptions object, except
+   *   where binary values are base64url encoded strings instead of byte arrays
+   *
+   * @return a PublicKeyCredentialCreationOptions suitable for passing as the
+   *   `publicKey` parameter to `navigator.credentials.create()`
+   */
+  function decodePublicKeyCredentialCreationOptions(request) {
+    const excludeCredentials = request.excludeCredentials.map(credential => extend(
+      credential, {
+      id: base64url.toByteArray(credential.id),
+    }));
+
+    const publicKeyCredentialCreationOptions = extend(
+      request, {
+      attestation: 'direct',
+      user: extend(
+        request.user, {
+        id: base64url.toByteArray(request.user.id),
+      }),
+      challenge: base64url.toByteArray(request.challenge),
+      excludeCredentials,
+    });
+
+    return publicKeyCredentialCreationOptions;
+  }
+
+  /**
+   * Create a WebAuthn credential.
+   *
+   * @param request: object - A PublicKeyCredentialCreationOptions object, except
+   *   where binary values are base64url encoded strings instead of byte arrays
+   *
+   * @return the Promise returned by `navigator.credentials.create`
+   */
+  function createCredential(request) {
+    return navigator.credentials.create({
+      publicKey: decodePublicKeyCredentialCreationOptions(request),
+    });
+  }
+
+  /**
+   * Perform a WebAuthn assertion.
+   *
+   * @param request: object - A PublicKeyCredentialRequestOptions object,
+   *   except where binary values are base64url encoded strings instead of byte
+   *   arrays
+   *
+   * @return a PublicKeyCredentialRequestOptions suitable for passing as the
+   *   `publicKey` parameter to `navigator.credentials.get()`
+   */
+  function decodePublicKeyCredentialRequestOptions(request) {
+    const allowCredentials = request.allowCredentials && request.allowCredentials.map(credential => extend(
+      credential, {
+      id: base64url.toByteArray(credential.id),
+    }));
+
+    const publicKeyCredentialRequestOptions = extend(
+      request, {
+      allowCredentials,
+      challenge: base64url.toByteArray(request.challenge),
+    });
+
+    return publicKeyCredentialRequestOptions;
+  }
+
+  /**
+   * Perform a WebAuthn assertion.
+   *
+   * @param request: object - A PublicKeyCredentialRequestOptions object,
+   *   except where binary values are base64url encoded strings instead of byte
+   *   arrays
+   *
+   * @return the Promise returned by `navigator.credentials.get`
+   */
+  function getAssertion(request) {
+    console.log('Get assertion', request);
+    return navigator.credentials.get({
+      publicKey: decodePublicKeyCredentialRequestOptions(request),
+    });
+  }
+
+
+  /** Turn a PublicKeyCredential object into a plain object with base64url encoded binary values */
+  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: bytesToBase64(response.response.attestationObject),
+            clientDataJSON: bytesToBase64(response.response.clientDataJSON),
+          },
+          clientExtensionResults,
+        };
+      } else {
+        return {
+          type: response.type,
+          id: response.id,
+          response: {
+            authenticatorData: base64url.fromByteArray(response.response.authenticatorData),
+            clientDataJSON: base64url.fromByteArray(response.response.clientDataJSON),
+            signature: base64url.fromByteArray(response.response.signature),
+            userHandle: response.response.userHandle && base64url.fromByteArray(response.response.userHandle),
+          },
+          clientExtensionResults,
+        };
+      }
+    }
+  }
+
+  return {
+    decodePublicKeyCredentialCreationOptions,
+    decodePublicKeyCredentialRequestOptions,
+    createCredential,
+    getAssertion,
+    responseToObject,
+  };
+
+});
\ 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 fce3aaa..3b6b4e2 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
@@ -27,18 +27,19 @@
     <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">
+    <script type="text/javascript" src="$request.getContextPath()/js/webauthn-support.js"></script>
     <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText(" idp.css", "/css/placeholder.css" )">
-
+    <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/webauthn.css">
     <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);
+                    document.getElementById("authenticationSubmit").click();
                 }).catch(function (err){console.error});    
 
      }
@@ -68,17 +69,23 @@
 
             <div class="content">
                 <div class="column one">
-                    <textarea id="publicKeyCredentialCreation" name="publicKeyCredentialCreation" rows="20" cols="50">
-                        $webauthnContext.publicKeyCredentialRequestOptions</textarea>
-                    <button id="authenticate" class="form-element form-button">Authenticate</button>
+                    <div class="centre">
+                        <button id="authenticate" class="form-element form-button">Authenticate</button>
+                    </div>
                     <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" type="submit" name="_eventId_proceed">Submit Authentication</button>
+                        <input type="hidden" id="publicKeyAssertion" name="publicKeyAssertion"/>
+                        <button class="hidden" id="authenticationSubmit" type="submit" name="_eventId_proceed">Submit Authentication</button>
                     </form>
-
-
+                    
+                    <hr/>
+                    <button type="button" class="collapsible">Debugging</button> 
+                    
+                    <div class="debug" id="debug-div">
+                      <label for="publicKeyCredentialCreation">Request Options</label>
+                      <textarea id="publicKeyCredentialRequestOptions" name="publicKeyCredentialRequestOptions" rows="20" cols="50">
+                        $webauthnContext.publicKeyCredentialRequestOptions</textarea>
+                    </div>
                 </div>
 
             </div>
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 a20ddba..c17545c 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
-## webauthnRegContext = web registration context
+## webauthnRegContext = WebAuthn registration context
 ## authenticationWarningContext - context with login warning state
 ## rpUIContext - the context with SP UI information from the metadata
 ## encoder - HTMLEncoder class
@@ -27,22 +27,25 @@
   <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">
+  <script type="text/javascript" src="$request.getContextPath()/js/webauthn-support.js"></script>
   <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("idp.css", "/css/placeholder.css" )">
-
+  <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/webauthn.css">
   <script type="module">
     import * as webauthnJson from "$request.getContextPath()/js/webauthn-json.js";
   
-    async function register() {
-        console.log("Register");
-        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 {
-            document.getElementById("publicKeyCredential").value = JSON.stringify(credentialJson);    
-        } catch (err) {
-            console.log(err);
-        }
+    async function register() {        
+        var pkCredOptions = $webauthnRegContext.publicKeyCredentialCreationOptionsJSON;               
+        await webauthnJson.create({ publicKey: pkCredOptions })
+                .then(function (attestation){
+                    console.log("attestation: ",attestation);
+                    var nickname = prompt("Credential Nickname");
+                    document.getElementById("credentialNickname").value = nickname;
+                    document.getElementById("authenticatorAttestation").value = JSON.stringify(attestation);    
+                    document.getElementById("registrationSubmit").click();
+                }).catch(function (err){
+                    console.error
+               });   
+
      };
 
      function init() {
@@ -72,33 +75,48 @@
       <section>
     
         <div class="content">
-          <div class="column one"> 
+          <div class="column one centre"> 
             <div>
                 <h1>Registered Credentials</h1>
                  #if ($webauthnRegContext.existingCredentials)
-                    <ul>
-                        #foreach($cred in $webauthnRegContext.existingCredentials)
-                            <li>$cred</li>
-                        #end
-                    </ul>
+                    <table>
+                        <tr>
+                            <th>Nickname</th>
+                            <th>Transports</th>
+                            <th>Registration Time</th>
+                            <th>Delete</th>
+                        </tr>
+                         #foreach($cred in $webauthnRegContext.existingCredentials)
+                            <tr>
+                                <td>$cred.nickname</td>
+                                <td>$cred.transports</td>
+                                <td>$cred.registrationTimestamp</td>
+                                <td>Remove</td>
+                            </tr>
+                         #end   
+                    </table>
                  #else
-                    <span>You have no registered credentials</span>                    
+                    <div><span>You have no registered credentials</span></div>                 
                  #end
-            <div>
-                <h1>Registered A New Credential</h1>
-                
-                
+                 <br/>
+                 <div>
+                    <button class="form-element form-button" id="registerButton">Add Key</button>
+                 </div>
+            </div>
+            <hr/>
+           
+            <button type="button" class="collapsible">Debugging</button> 
+            
+            <div class="debug" id="debug-div">
                 <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">
+                <form id="authenticatorAttestationForm" 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>
+                  <input type="hidden" id="credentialNickname" name="credentialNickname"/>
+                  <input type="hidden" id="authenticatorAttestation" name="authenticatorAttestation"/>
+                  <button class="hidden" id="registrationSubmit" type="submit" name="_eventId_proceed">Submit Registration</button>
                 </form>
             </div>
     
diff --git a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.vm b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.vm
index 2ed681d..33ad029 100644
--- a/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.vm
+++ b/webauthn-impl/src/main/resources/net/shibboleth/idp/plugin/authn/webauthn/views/webauthn-registered.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 = WebAuthn registration context
 ## authenticationWarningContext - context with login warning state
 ## rpUIContext - the context with SP UI information from the metadata
 ## encoder - HTMLEncoder class
@@ -21,37 +21,72 @@
 ##
 <!DOCTYPE html>
 <html>
-    <head>
-        <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">
-        <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("idp.css", "/css/placeholder.css")">
-    </head>
-    
-    
-    
-    <body>
-        <main class="main">
-            <header>
-                <img class="main-logo" src="$request.getContextPath()#springMessageText("idp.logo", "/images/placeholder-logo.png")" alt="#springMessageText("idp.logo.alt-text", "logo")" />
-                
-                #set ($serviceName = $rpUIContext.serviceName)
-                #if ($serviceName && !$rpContext.getRelyingPartyId().contains($serviceName))
-                    <h1>#springMessageText("idp.login.loginTo", "Login to") $encoder.encodeForHTML($serviceName)</h1>
+
+<head>
+    <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">
+    <link rel="stylesheet" type="text/css" href="$request.getContextPath()#springMessageText("
+        idp.css", "/css/placeholder.css" )">
+    <link rel="stylesheet" type="text/css" href="$request.getContextPath()/css/webauthn.css">
+</head>
+
+
+
+<body>
+    <main class="main">
+        <header>
+            <img class="main-logo" src="$request.getContextPath()#springMessageText("
+                idp.logo", "/images/placeholder-logo.png" )" alt="#springMessageText(" idp.logo.alt-text", "logo" )" />
+
+            #set ($serviceName = $rpUIContext.serviceName)
+            #if ($serviceName && !$rpContext.getRelyingPartyId().contains($serviceName))
+            <h1>#springMessageText("idp.login.loginTo", "Login to") $encoder.encodeForHTML($serviceName)</h1>
+            #end
+        </header>
+        <section>
+            <div class="centre">
+                <p>You have registered your authenticator credentials successfully.</p>
+
+                <hr />
+                <p>Registered credentials</p>
+                #if ($webauthnRegContext.existingCredentials)
+                <table>
+                    <tr>
+                        <th>Nickname</th>
+                        <th>Transports</th>
+                        <th>Registration Time</th>
+                    </tr>
+                    #foreach($cred in $webauthnRegContext.existingCredentials)
+                    <tr>
+                        <td>$cred.nickname</td>
+                        <td>$cred.transports</td>
+                        <td>$cred.registrationTimestamp</td>
+                    </tr>
+                    #end
+                </table>
+                #else
+                <div><span>You have no registered credentials</span></div>
                 #end
-            </header>
-            <section>
-                You have registered your authenticator credentials succesfully.
-            </section>
-        </main>
+                <br />
+                <form id="doneButtonForm" action="$flowExecutionUrl" method="post">
+                    #parse("csrf/csrf.vm")
+                    <button id="doneButton" type="submit" name="_eventId_proceed">Done</button>
+                </form>
+            </div>
+
+
+        </section>
+    </main>
 
-      <footer>
+    <footer>
         <div class="container container-footer">
-          <p class="footer-text">#springMessageText("idp.footer", "Insert your footer text here.")</p>
+            <p class="footer-text">#springMessageText("idp.footer", "Insert your footer text here.")</p>
         </div>
-      </footer>
+    </footer>
     </div>
-    
-     </body>
-</html>
\ No newline at end of file
+
+</body>
+
+</html>
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
index d07a1da..a12e6ea 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/client/impl/YubicoWebauthnAuthenticationClientTest.java
@@ -33,8 +33,13 @@ import com.yubico.webauthn.AssertionResult;
 import com.yubico.webauthn.RegisteredCredential;
 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.AuthenticatorTransport;
 import com.yubico.webauthn.data.ByteArray;
+import com.yubico.webauthn.data.ClientAssertionExtensionOutputs;
+import com.yubico.webauthn.data.ClientRegistrationExtensionOutputs;
+import com.yubico.webauthn.data.PublicKeyCredential;
 import com.yubico.webauthn.data.PublicKeyCredentialCreationOptions;
 import com.yubico.webauthn.data.PublicKeyCredentialRequestOptions;
 import com.yubico.webauthn.data.RelyingPartyIdentity;
@@ -45,9 +50,7 @@ import net.shibboleth.idp.plugin.authn.webauthn.AssertionFailureException;
 import net.shibboleth.idp.plugin.authn.webauthn.RegistrationFailureException;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.AbstractWebAuthnTest;
 import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator.Attestation;
-import net.shibboleth.idp.plugin.authn.webauthn.impl.MockAuthenticator.AuthenticatonExtensionsClientOutputs;
-import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.CredentialRegistration;
+import net.shibboleth.idp.plugin.authn.webauthn.storage.CredentialRegistration;
 import net.shibboleth.idp.plugin.authn.webauthn.storage.impl.InMemoryRegistrationStorage;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.codec.Base64Support;
@@ -136,13 +139,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
         
-        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
                         Base64Support.decode(USER_HANDLE_B64));
         
-        final var attestationJson = jsonMapper.writeValueAsString(attestation);
         final RegistrationResult registration = 
-                client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestationJson);
+                client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
         
         assertNotNull(registration);
 
@@ -155,13 +157,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
         
-        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
                         Base64Support.decode(USER_HANDLE_B64));
         
-        final var attestationJson = jsonMapper.writeValueAsString(attestation);
         final RegistrationResult registration = 
-                client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestationJson);
+                client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
         
         assertNotNull(registration);
     }
@@ -173,13 +174,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", "wrong-origin", CHALLENGE_B64); 
            
-        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
                         Base64Support.decode(USER_HANDLE_B64));
         
-        final var attestationJson = jsonMapper.writeValueAsString(attestation);
         final RegistrationResult registration = 
-                client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestationJson);
+                client.validateAuthenticatorAttestationResponse(credentialCreationOptions, attestation);
 
     }
     
@@ -191,14 +191,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
         
         // Need to register a new credential first
-        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
                         Base64Support.decode(USER_HANDLE_B64));
         
         final RegisteredCredential credential = RegisteredCredential.builder()
-                .credentialId(new ByteArray(attestation.getRawId()))
+                .credentialId(attestation.getId())
                 .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
-                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+                        .getAttestedCredentialData().get().getCredentialPublicKey())
                 .build();
         
         final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
@@ -209,14 +210,13 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64); 
         
         // Now generate an assertion (authentication) and check it is valid
-        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
-            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
-        
-        final var assertionJson = jsonMapper.writeValueAsString(assertion);
-        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                    clientDataGet);
+
         final AssertionResult result = 
                 client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
-                credentialRequestOptions, assertionJson);
+                credentialRequestOptions, assertion);
         assertNotNull(result);
         assertTrue(result.isSuccess());
         
@@ -230,14 +230,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
         
         // Need to register a new credential first
-        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
                         Base64Support.decode(USER_HANDLE_B64));
         
         final RegisteredCredential credential = RegisteredCredential.builder()
-                .credentialId(new ByteArray(attestation.getRawId()))
+                .credentialId(attestation.getId())
                 .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
-                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+                        .getAttestedCredentialData().get().getCredentialPublicKey())
                 .build();
         
         final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
@@ -249,13 +250,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataGet = createClientData("webauthn.get", "wrong", CHALLENGE_B64); 
         
         // Now generate an assertion (authentication) and check it is valid
-        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
-            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                    clientDataGet);
         
-        final var assertionJson = jsonMapper.writeValueAsString(assertion);
-        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
         client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
-                credentialRequestOptions, assertionJson);
+                credentialRequestOptions, assertion);
         
     }
     
@@ -267,14 +267,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
         
         // Need to register a new credential first
-        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
                         Base64Support.decode(USER_HANDLE_B64));
         
         final RegisteredCredential credential = RegisteredCredential.builder()
-                .credentialId(new ByteArray(attestation.getRawId()))
+                .credentialId(attestation.getId())
                 .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
-                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+                        .getAttestedCredentialData().get().getCredentialPublicKey())
                 .build();
         
         final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
@@ -286,13 +287,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataGet = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
         
         // Now generate an assertion (authentication) and check it is valid
-        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
-            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
-        
-        final var assertionJson = jsonMapper.writeValueAsString(assertion);
-        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+        assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                clientDataGet);
+    
         client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
-                credentialRequestOptions, assertionJson);
+            credentialRequestOptions, assertion);
            
     }
     
@@ -305,14 +305,15 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataCreate = createClientData("webauthn.create", ORIGIN, CHALLENGE_B64); 
         
         // Need to register a new credential first
-        final MockAuthenticator.PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> attestation = 
+        final PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs> attestation = 
                 mockAuthenticator.createAuthenticatorAttestationResponse(CHALLENGE_B64, clientDataCreate, 
                         Base64Support.decode(USER_HANDLE_B64));
         
         final RegisteredCredential credential = RegisteredCredential.builder()
-                .credentialId(new ByteArray(attestation.getRawId()))
+                .credentialId(attestation.getId())
                 .userHandle(new ByteArray(Base64Support.decode(USER_HANDLE_B64)))
-                .publicKeyCose(new ByteArray(attestation.getResponse().getKey().AsCBOR().EncodeToBytes()))
+                .publicKeyCose(attestation.getResponse().getParsedAuthenticatorData()
+                        .getAttestedCredentialData().get().getCredentialPublicKey())
                 .build();
         
         final CredentialRegistration reg = new CredentialRegistration(userIdentity, Optional.of("Nickanme"), 
@@ -324,13 +325,12 @@ public class YubicoWebauthnAuthenticationClientTest extends AbstractWebAuthnTest
         final Map<String, String> clientDataGet = createClientData("webauthn.get", ORIGIN, CHALLENGE_B64); 
         
         // Now generate an assertion (authentication) and check it is valid
-        final MockAuthenticator.PublicKeyCredential<MockAuthenticator.Assertion, AuthenticatonExtensionsClientOutputs> 
-            assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getRawId(), clientDataGet);
-        
-        final var assertionJson = jsonMapper.writeValueAsString(assertion);
-        log.debug("AuthenticatorAssertionResponse: '{}'",assertionJson);
+        final PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+        assertion = mockAuthenticator.createAuthenticatorAssertionResponse(attestation.getId().getBytes(), 
+                clientDataGet);
+    
         client.validateAuthenticatorAssertionResponse(USERNAME, Base64Support.decode(USER_HANDLE_B64), 
-                credentialRequestOptions, assertionJson);
+            credentialRequestOptions, assertion);
         
     }
     
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
index 51ebed8..c73e677 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/MockAuthenticator.java
@@ -48,6 +48,10 @@ import com.fasterxml.jackson.databind.json.JsonMapper;
 import com.fasterxml.jackson.dataformat.cbor.CBORFactory;
 import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
 import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+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 COSE.AlgorithmID;
 import COSE.OneKey;
@@ -148,7 +152,7 @@ public class MockAuthenticator {
      * 
      * @throws Exception on error.
      */
-    public PublicKeyCredential<Attestation, AuthenticatonExtensionsClientOutputs> 
+    public com.yubico.webauthn.data.PublicKeyCredential<AuthenticatorAttestationResponse, ClientRegistrationExtensionOutputs>  
         createAuthenticatorAttestationResponse(@Nonnull @NotEmpty final String challenge,
                 final Map<String, String> clientData, final byte[] userHandle) throws Exception {
         
@@ -166,8 +170,13 @@ public class MockAuthenticator {
                 credentialId, 
                 new Attestation(clientDataJsonString.getBytes(), createdKey, attestationObject, userHandle),
                 new AuthenticatonExtensionsClientOutputs());
+        
+        //convert from the test type to the correct type for the rest of the system
+        final var attestationAsJson = jsonMapper.writeValueAsString(publicKeyCredential);
+        final var pkCred =com.yubico.webauthn.data.PublicKeyCredential.parseRegistrationResponseJson(attestationAsJson);
+
         createdCredentialsMaps.put(Base64Support.encodeURLSafe(credentialId), publicKeyCredential);
-        return publicKeyCredential;
+        return pkCred;
         
     }
     
@@ -181,8 +190,9 @@ public class MockAuthenticator {
      * 
      * @throws Exception on error
      */
-    public PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs> createAuthenticatorAssertionResponse(
-            @Nonnull final byte[] credentialId, final Map<String, String> clientData) throws Exception {
+    public com.yubico.webauthn.data.PublicKeyCredential<AuthenticatorAssertionResponse, ClientAssertionExtensionOutputs> 
+        createAuthenticatorAssertionResponse(@Nonnull final byte[] credentialId, final Map<String, String> clientData) 
+                throws Exception {
         
         final var credentialb64 = Base64Support.encodeURLSafe(credentialId);
         final var publicKeyAttestation = createdCredentialsMaps.get(credentialb64);
@@ -198,10 +208,15 @@ public class MockAuthenticator {
                 sign(authenticatorData, clientDataCompactSerialization, 
                         publicKeyAttestation.getResponse().getKey().AsPrivateKey());
         
-        return new PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs>(rawCredentialIdentifier,
+        final var pkCred = new PublicKeyCredential<Assertion, AuthenticatonExtensionsClientOutputs>(rawCredentialIdentifier,
                 new Assertion(clientDataCompactSerialization.getBytes(), authenticatorData, signature, 
                         publicKeyAttestation.getResponse().getUserHandle()),
                 new AuthenticatonExtensionsClientOutputs());
+        
+        //convert from the test type to the correct type for the rest of the system
+        final var assertionAsJson = jsonMapper.writeValueAsString(pkCred);
+        return com.yubico.webauthn.data.PublicKeyCredential.parseAssertionResponseJson(assertionAsJson);
+        
     }
     
     /**
diff --git a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
index 8b6a85c..8715c85 100644
--- a/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
+++ b/webauthn-impl/src/test/java/net/shibboleth/idp/plugin/authn/webauthn/impl/ValidatePublicKeyCredentialTest.java
@@ -76,6 +76,7 @@ public class ValidatePublicKeyCredentialTest extends AbstractWebAuthnTest{
                 
         final WebAuthnAuthenticationClient client = new YubicoWebauthnAuthenticationClient(rp, jsonMapper);
         validator.setWebAuthnClient(client);
+        validator.setCredentialRepository(new InMemoryRegistrationStorage());
         validator.initialize();
     }
     

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


More information about the commits mailing list