From c4c27dc6dbf3bcb8509cfdd9d6ce112f37c275b5 Mon Sep 17 00:00:00 2001
From: Justin Cranford <justincranford@hotmail.com>
Date: Mon, 20 Jan 2025 01:21:38 -0500
Subject: [PATCH] Add serialize/deserialize PublicKeyCredentialCreationOptions
 and PublicKeyCredentialRequestOptions support with ObjectMapper

Signed-off-by: Justin Cranford <justincranford@hotmail.com>

gh-16433 PublicKeyCredentialRequestOptions
gh-16434 PublicKeyCredentialCreationOptions
---
 web/spring-security-web.gradle                |   1 +
 .../webauthn/api/AuthenticatorTransport.java  |   3 +-
 .../webauthn/api/COSEAlgorithmIdentifier.java |  21 ++++
 .../api/PublicKeyCredentialParameters.java    |  22 ++++
 .../api/UserVerificationRequirement.java      |  21 ++++
 ...tionExtensionsClientInputDeserializer.java |  51 ++++++++
 ...henticationExtensionsClientInputMixin.java |   4 +-
 ...ionExtensionsClientInputsDeserializer.java |  55 +++++++++
 ...enticationExtensionsClientInputsMixin.java |   2 +
 ...nticatorSelectionCriteriaDeserializer.java |  64 ++++++++++
 .../AuthenticatorSelectionCriteriaMixin.java  |   5 +
 ...henticatorSelectionCriteriaSerializer.java |  58 +++++++++
 .../AuthenticatorTransportDeserializer.java   |  16 +--
 .../AuthenticatorTransportSerializer.java     |   1 +
 .../COSEAlgorithmIdentifierDeserializer.java  |  16 +--
 .../jackson/DurationDeserializer.java         |  48 ++++++++
 ...blicKeyCredentialCreationOptionsMixin.java |  37 ++++--
 .../PublicKeyCredentialDescriptorMixin.java   |  42 +++++++
 ...icKeyCredentialParametersDeserializer.java |  60 ++++++++++
 .../PublicKeyCredentialParametersMixin.java   |  32 +++++
 ...blicKeyCredentialParametersSerializer.java |  52 ++++++++
 ...ublicKeyCredentialRequestOptionsMixin.java |  30 +++--
 .../PublicKeyCredentialRpEntityMixin.java     |  36 ++++++
 ...icKeyCredentialUserEntityDeserializer.java |  64 ++++++++++
 .../PublicKeyCredentialUserEntityMixin.java   |  32 +++++
 ...blicKeyCredentialUserEntitySerializer.java |  56 +++++++++
 .../ResidentKeyRequirementDeserializer.java   |  45 +++++++
 .../jackson/ResidentKeyRequirementMixin.java  |   4 +-
 ...erVerificationRequirementDeserializer.java |  45 +++++++
 .../UserVerificationRequirementMixin.java     |   4 +-
 .../jackson/WebauthnJackson2Module.java       |  10 ++
 .../web/webauthn/jackson/JacksonTests.java    |   2 +
 ...thAuthenticationExtensionsClientInput.java |  34 ++++++
 ...licKeyCredentialCreationOptionsGivens.java | 113 ++++++++++++++++++
 ...blicKeyCredentialCreationOptionsTests.java |  62 ++++++++++
 ...blicKeyCredentialRequestOptionsGivens.java |  75 ++++++++++++
 ...ublicKeyCredentialRequestOptionsTests.java |  62 ++++++++++
 37 files changed, 1241 insertions(+), 44 deletions(-)
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputDeserializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsDeserializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaDeserializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaSerializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationDeserializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialDescriptorMixin.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersDeserializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersMixin.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersSerializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRpEntityMixin.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityDeserializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityMixin.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntitySerializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementDeserializer.java
 create mode 100644 web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementDeserializer.java
 create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/jackson/MinPinLengthAuthenticationExtensionsClientInput.java
 create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsGivens.java
 create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsTests.java
 create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsGivens.java
 create mode 100644 web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsTests.java

diff --git a/web/spring-security-web.gradle b/web/spring-security-web.gradle
index a709c95a991..4edead601bd 100644
--- a/web/spring-security-web.gradle
+++ b/web/spring-security-web.gradle
@@ -36,6 +36,7 @@ dependencies {
 	api 'org.springframework:spring-web'
 
 	optional 'com.fasterxml.jackson.core:jackson-databind'
+	optional 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
 	optional 'io.projectreactor:reactor-core'
 	optional 'org.springframework:spring-jdbc'
 	optional 'org.springframework:spring-tx'
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java b/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java
index 33e9d2123cb..7c8706089dc 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/api/AuthenticatorTransport.java
@@ -23,6 +23,7 @@
  * order to obtain an assertion for a specific credential.
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
 public final class AuthenticatorTransport {
@@ -112,7 +113,7 @@ public static AuthenticatorTransport valueOf(String value) {
 	}
 
 	public static AuthenticatorTransport[] values() {
-		return new AuthenticatorTransport[] { USB, NFC, BLE, HYBRID, INTERNAL };
+		return new AuthenticatorTransport[] { USB, NFC, BLE, SMART_CARD, HYBRID, INTERNAL };
 	}
 
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java b/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java
index 0cafd9309bd..4510f9c010e 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/api/COSEAlgorithmIdentifier.java
@@ -22,6 +22,7 @@
  * used to identify a cryptographic algorithm.
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  * @see PublicKeyCredentialParameters#getAlg()
  */
@@ -62,4 +63,24 @@ public static COSEAlgorithmIdentifier[] values() {
 		return new COSEAlgorithmIdentifier[] { EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, RS1 };
 	}
 
+	public static COSEAlgorithmIdentifier valueOf(long value) {
+		if (COSEAlgorithmIdentifier.EdDSA.getValue() == value) {
+			return EdDSA;
+		} else if (COSEAlgorithmIdentifier.ES256.getValue() == value) {
+			return ES256;
+		} else if (COSEAlgorithmIdentifier.ES384.getValue() == value) {
+			return ES384;
+		} else if (COSEAlgorithmIdentifier.ES512.getValue() == value) {
+			return ES512;
+		} else if (COSEAlgorithmIdentifier.RS256.getValue() == value) {
+			return RS256;
+		} else if (COSEAlgorithmIdentifier.RS384.getValue() == value) {
+			return RS384;
+		} else if (COSEAlgorithmIdentifier.RS512.getValue() == value) {
+			return RS512;
+		} else if (COSEAlgorithmIdentifier.RS1.getValue() == value) {
+			return RS1;
+		}
+		return new COSEAlgorithmIdentifier(value);
+	}
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java b/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java
index abb8c028330..db91ae70e39 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/api/PublicKeyCredentialParameters.java
@@ -22,6 +22,7 @@
  * is used to supply additional parameters when creating a new credential.
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  * @see PublicKeyCredentialCreationOptions#getPubKeyCredParams()
  */
@@ -96,4 +97,25 @@ public COSEAlgorithmIdentifier getAlg() {
 		return this.alg;
 	}
 
+	public static PublicKeyCredentialParameters valueOf(PublicKeyCredentialType type, COSEAlgorithmIdentifier alg) {
+		if (PublicKeyCredentialParameters.EdDSA.getType().equals(type) && PublicKeyCredentialParameters.EdDSA.getAlg().equals(alg)) {
+			return EdDSA;
+		} else if (PublicKeyCredentialParameters.ES256.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
+			return ES256;
+		} else if (PublicKeyCredentialParameters.ES384.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
+			return ES384;
+		} else if (PublicKeyCredentialParameters.ES512.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
+			return ES512;
+		} else if (PublicKeyCredentialParameters.RS256.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
+			return RS256;
+		} else if (PublicKeyCredentialParameters.RS384.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
+			return RS384;
+		} else if (PublicKeyCredentialParameters.RS512.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
+			return RS512;
+		} else if (PublicKeyCredentialParameters.RS1.getType().equals(type) && PublicKeyCredentialParameters.ES256.getAlg().equals(alg)) {
+			return RS1;
+		}
+		return new PublicKeyCredentialParameters(type, alg);
+	}
+
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java b/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java
index adc915015b5..5d23f1a37e9 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/api/UserVerificationRequirement.java
@@ -22,6 +22,7 @@
  * is used by the Relying Party to indicate if user verification is needed.
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
 public final class UserVerificationRequirement {
@@ -66,4 +67,24 @@ public String getValue() {
 		return this.value;
 	}
 
+	/**
+	 * Gets the value
+	 * @param value the string
+	 * @return the value
+	 * @author Justin Cranford
+	 * @since 6.5
+	 */
+	public static UserVerificationRequirement valueOf(String value) {
+		if (DISCOURAGED.getValue().equals(value)) {
+			return DISCOURAGED;
+		}
+		if (PREFERRED.getValue().equals(value)) {
+			return PREFERRED;
+		}
+		if (REQUIRED.getValue().equals(value)) {
+			return REQUIRED;
+		}
+		return new UserVerificationRequirement(value);
+	}
+
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputDeserializer.java
new file mode 100644
index 00000000000..c16c60614a4
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputDeserializer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
+import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInput;
+
+import java.io.IOException;
+
+/**
+ * Jackson deserializer for {@link AuthenticationExtensionsClientInput}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+class AuthenticationExtensionsClientInputDeserializer extends StdDeserializer<AuthenticationExtensionsClientInput> {
+
+	AuthenticationExtensionsClientInputDeserializer() {
+		super(AuthenticationExtensionsClientInput.class);
+	}
+
+	@Override
+	public AuthenticationExtensionsClientInput deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		if (parser.nextToken() != JsonToken.END_OBJECT) {
+			String extensionId = parser.currentName();
+			Object value = parser.readValueAs(Object.class);
+			return new ImmutableAuthenticationExtensionsClientInput(extensionId, value);
+		}
+		return null;
+	}
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java
index 7c8e4c664b3..3969d017261 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputMixin.java
@@ -16,17 +16,19 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
 import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
 
 /**
  * Jackson mixin for {@link AuthenticationExtensionsClientInputs}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
 @JsonSerialize(using = AuthenticationExtensionsClientInputSerializer.class)
+@JsonDeserialize(using = AuthenticationExtensionsClientInputDeserializer.class)
 class AuthenticationExtensionsClientInputMixin {
 
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsDeserializer.java
new file mode 100644
index 00000000000..5d84bcf6fb3
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsDeserializer.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
+import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
+import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Jackson deserializer for {@link AuthenticationExtensionsClientInputs}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+class AuthenticationExtensionsClientInputsDeserializer extends StdDeserializer<AuthenticationExtensionsClientInputs> {
+
+	AuthenticationExtensionsClientInputsDeserializer() {
+		super(AuthenticationExtensionsClientInputs.class);
+	}
+
+	@Override
+	public AuthenticationExtensionsClientInputs deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		final AuthenticationExtensionsClientInputDeserializer authenticationExtensionsClientInputDeserializer = new AuthenticationExtensionsClientInputDeserializer();
+
+		final List<AuthenticationExtensionsClientInput> extensions = new ArrayList<>();
+		while (parser.nextToken() != JsonToken.END_OBJECT) {
+			extensions.add(authenticationExtensionsClientInputDeserializer.deserialize(parser, ctxt));
+		}
+		return new ImmutableAuthenticationExtensionsClientInputs(extensions);
+	}
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java
index 9cbe52556ff..9d7fa04a1b0 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticationExtensionsClientInputsMixin.java
@@ -16,6 +16,7 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 
 import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
@@ -27,6 +28,7 @@
  * @since 6.4
  */
 @JsonSerialize(using = AuthenticationExtensionsClientInputsSerializer.class)
+@JsonDeserialize(using = AuthenticationExtensionsClientInputsDeserializer.class)
 class AuthenticationExtensionsClientInputsMixin {
 
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaDeserializer.java
new file mode 100644
index 00000000000..989fb5488fc
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaDeserializer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
+import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
+import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria.AuthenticatorSelectionCriteriaBuilder;
+import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
+import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
+
+import java.io.IOException;
+
+/**
+ * Jackson deserializer for {@link AuthenticatorSelectionCriteria}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+class AuthenticatorSelectionCriteriaDeserializer extends StdDeserializer<AuthenticatorSelectionCriteria> {
+
+	AuthenticatorSelectionCriteriaDeserializer() {
+		super(AuthenticatorSelectionCriteria.class);
+	}
+
+	@Override
+	public AuthenticatorSelectionCriteria deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		final AuthenticatorSelectionCriteriaBuilder builder = AuthenticatorSelectionCriteria.builder();
+		while (parser.nextToken() != JsonToken.END_OBJECT) {
+			final String fieldName = parser.currentName();
+			parser.nextToken();
+			if ("authenticatorAttachment".equals(fieldName)) {
+				builder.authenticatorAttachment(AuthenticatorAttachment.valueOf(parser.getText()));
+			} else if ("residentKey".equals(fieldName)) {
+				builder.residentKey(ResidentKeyRequirement.valueOf(parser.getText()));
+			} else if ("userVerification".equals(fieldName)) {
+				builder.userVerification(UserVerificationRequirement.valueOf(parser.getText()));
+			} else {
+				throw new IOException("Unsupported field name: " + fieldName);
+			}
+		}
+		return builder.build();
+	}
+
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java
index 5fb51f53ba2..2680e9fc867 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaMixin.java
@@ -18,14 +18,19 @@
 
 import com.fasterxml.jackson.annotation.JsonInclude;
 
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
 
 /**
  * Jackson mixin for {@link AuthenticatorSelectionCriteria}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
+@JsonSerialize(using = AuthenticatorSelectionCriteriaSerializer.class)
+@JsonDeserialize(using = AuthenticatorSelectionCriteriaDeserializer.class)
 @JsonInclude(JsonInclude.Include.NON_NULL)
 abstract class AuthenticatorSelectionCriteriaMixin {
 
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaSerializer.java
new file mode 100644
index 00000000000..7bc1d266b9c
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorSelectionCriteriaSerializer.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
+
+import java.io.IOException;
+
+/**
+ * Jackson serializer for {@link AuthenticatorSelectionCriteria}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+class AuthenticatorSelectionCriteriaSerializer extends StdSerializer<AuthenticatorSelectionCriteria> {
+
+	AuthenticatorSelectionCriteriaSerializer() {
+		super(AuthenticatorSelectionCriteria.class);
+	}
+
+	@Override
+	public void serialize(AuthenticatorSelectionCriteria value, JsonGenerator gen, SerializerProvider provider)
+			throws IOException {
+		gen.writeStartObject();
+		if (value.getAuthenticatorAttachment() != null) {
+			gen.writeFieldName("authenticatorAttachment");
+			gen.writeString(value.getAuthenticatorAttachment().getValue());
+		}
+		if (value.getResidentKey() != null) {
+			gen.writeFieldName("residentKey");
+			gen.writeString(value.getResidentKey().getValue());
+		}
+		if (value.getUserVerification() != null) {
+			gen.writeFieldName("userVerification");
+			gen.writeString(value.getUserVerification().getValue());
+		}
+		gen.writeEndObject();
+	}
+
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java
index 8cafd92aa96..4b7d83dc00c 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportDeserializer.java
@@ -16,19 +16,18 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JacksonException;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-
 import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
 
+import java.io.IOException;
+
 /**
  * Jackson deserializer for {@link AuthenticatorTransport}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
 @SuppressWarnings("serial")
@@ -40,14 +39,9 @@ class AuthenticatorTransportDeserializer extends StdDeserializer<AuthenticatorTr
 
 	@Override
 	public AuthenticatorTransport deserialize(JsonParser parser, DeserializationContext ctxt)
-			throws IOException, JacksonException {
+			throws IOException {
 		String transportValue = parser.readValueAs(String.class);
-		for (AuthenticatorTransport transport : AuthenticatorTransport.values()) {
-			if (transport.getValue().equals(transportValue)) {
-				return transport;
-			}
-		}
-		return null;
+		return AuthenticatorTransport.valueOf(transportValue);
 	}
 
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportSerializer.java
index ea4a52a3586..152502d0b03 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportSerializer.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/AuthenticatorTransportSerializer.java
@@ -30,6 +30,7 @@
  * @author Rob Winch
  * @since 6.4
  */
+@SuppressWarnings("serial")
 class AuthenticatorTransportSerializer extends JsonSerializer<AuthenticatorTransport> {
 
 	@Override
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java
index ed1e6e48370..20bd538f9a4 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/COSEAlgorithmIdentifierDeserializer.java
@@ -16,19 +16,18 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
-import java.io.IOException;
-
-import com.fasterxml.jackson.core.JacksonException;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
-
 import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
 
+import java.io.IOException;
+
 /**
  * Jackson serializer for {@link COSEAlgorithmIdentifier}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
 @SuppressWarnings("serial")
@@ -40,14 +39,9 @@ class COSEAlgorithmIdentifierDeserializer extends StdDeserializer<COSEAlgorithmI
 
 	@Override
 	public COSEAlgorithmIdentifier deserialize(JsonParser parser, DeserializationContext ctxt)
-			throws IOException, JacksonException {
+			throws IOException {
 		Long transportValue = parser.readValueAs(Long.class);
-		for (COSEAlgorithmIdentifier identifier : COSEAlgorithmIdentifier.values()) {
-			if (identifier.getValue() == transportValue.longValue()) {
-				return identifier;
-			}
-		}
-		return null;
+		return COSEAlgorithmIdentifier.valueOf(transportValue);
 	}
 
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationDeserializer.java
new file mode 100644
index 00000000000..4b0e98b6d3e
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/DurationDeserializer.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import java.io.IOException;
+import java.time.Duration;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+
+/**
+ * Jackson deserializer for {@link Duration}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+class DurationDeserializer extends StdDeserializer<Duration> {
+
+	/**
+	 * Creates an instance.
+	 */
+	DurationDeserializer() {
+		super(Duration.class);
+	}
+
+	@Override
+	public Duration deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		long millis = parser.getLongValue();
+		return Duration.ofMillis(millis);
+	}
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java
index 95eaea0fd40..6d5b7949774 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsMixin.java
@@ -16,23 +16,42 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
-import java.time.Duration;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
+import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
+import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
+import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
+import org.springframework.security.web.webauthn.api.Bytes;
 import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
+
+import java.time.Duration;
+import java.util.List;
 
 /**
  * Jackson mixin for {@link PublicKeyCredentialCreationOptions}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
-@JsonInclude(JsonInclude.Include.NON_NULL)
 abstract class PublicKeyCredentialCreationOptionsMixin {
-
-	@JsonSerialize(using = DurationSerializer.class)
-	private Duration timeout;
-
+	@JsonCreator
+	public PublicKeyCredentialCreationOptionsMixin(
+		@JsonProperty("rp") PublicKeyCredentialRpEntity rp,
+		@JsonProperty("user") PublicKeyCredentialUserEntity user,
+		@JsonProperty("challenge") Bytes challenge,
+		@JsonProperty("pubKeyCredParams") List<PublicKeyCredentialParameters> pubKeyCredParams,
+		@JsonProperty("timeout") @JsonSerialize(using=DurationSerializer.class) @JsonDeserialize(using=DurationDeserializer.class) Duration timeout,
+		@JsonProperty("excludeCredentials") List<PublicKeyCredentialDescriptor> excludeCredentials,
+		@JsonProperty("authenticatorSelection") AuthenticatorSelectionCriteria authenticatorSelection,
+		@JsonProperty("attestation") AttestationConveyancePreference attestation,
+		@JsonProperty("extensions") AuthenticationExtensionsClientInputs extensions
+	) {
+	}
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialDescriptorMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialDescriptorMixin.java
new file mode 100644
index 00000000000..a7024fc2235
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialDescriptorMixin.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
+import org.springframework.security.web.webauthn.api.Bytes;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
+
+import java.util.Set;
+
+/**
+ * Jackson mixin for {@link PublicKeyCredentialDescriptor}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+abstract class PublicKeyCredentialDescriptorMixin {
+	@JsonCreator
+	public PublicKeyCredentialDescriptorMixin(
+		@JsonProperty("type") PublicKeyCredentialType type,
+		@JsonProperty("id") Bytes id,
+		@JsonProperty("transports") Set<AuthenticatorTransport> transports
+	) {
+	}
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersDeserializer.java
new file mode 100644
index 00000000000..3e1cb00e318
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersDeserializer.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.springframework.security.web.webauthn.api.COSEAlgorithmIdentifier;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
+
+import java.io.IOException;
+
+/**
+ * Jackson deserializer for {@link PublicKeyCredentialParameters}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+public class PublicKeyCredentialParametersDeserializer extends StdDeserializer<PublicKeyCredentialParameters> {
+
+    public PublicKeyCredentialParametersDeserializer() {
+        super(PublicKeyCredentialParameters.class);
+    }
+
+    @Override
+    public PublicKeyCredentialParameters deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		PublicKeyCredentialType type = null;
+		COSEAlgorithmIdentifier alg = null;
+        while (parser.nextToken() != JsonToken.END_OBJECT) {
+            final String fieldName = parser.currentName();
+			parser.nextToken();
+			if ("type".equals(fieldName)) {
+				type = PublicKeyCredentialType.valueOf(parser.getText());
+            } else if ("alg".equals(fieldName)) {
+				alg = COSEAlgorithmIdentifier.valueOf(parser.getLongValue());
+            } else {
+				throw new IOException("Unsupported field name: " + fieldName);
+            }
+        }
+        return PublicKeyCredentialParameters.valueOf(type, alg);
+    }
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersMixin.java
new file mode 100644
index 00000000000..efc79498071
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersMixin.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
+
+/**
+ * Jackson mixin for {@link PublicKeyCredentialParameters}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@JsonSerialize(using = PublicKeyCredentialParametersSerializer.class)
+@JsonDeserialize(using = PublicKeyCredentialParametersDeserializer.class)
+abstract class PublicKeyCredentialParametersMixin {
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersSerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersSerializer.java
new file mode 100644
index 00000000000..64276ce6645
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialParametersSerializer.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
+
+import java.io.IOException;
+
+/**
+ * Jackson serializer for {@link PublicKeyCredentialParameters}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+public class PublicKeyCredentialParametersSerializer extends StdSerializer<PublicKeyCredentialParameters> {
+
+    public PublicKeyCredentialParametersSerializer() {
+        super(PublicKeyCredentialParameters.class);
+    }
+
+    @Override
+    public void serialize(PublicKeyCredentialParameters value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+        gen.writeStartObject();
+		if (value.getType() != null) {
+			gen.writeFieldName("type");
+			gen.writeString(value.getType().getValue());
+		}
+		if (value.getAlg() != null) {
+			gen.writeFieldName("alg");
+			gen.writeNumber(value.getAlg().getValue());
+		}
+        gen.writeEndObject();
+    }
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java
index 9062c5faaac..4749332851f 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsMixin.java
@@ -16,23 +16,35 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
-import java.time.Duration;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
+import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInputs;
+import org.springframework.security.web.webauthn.api.Bytes;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
 import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
+import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
+
+import java.time.Duration;
+import java.util.List;
 
 /**
  * Jackson mixin for {@link PublicKeyCredentialRequestOptions}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
-@JsonInclude(content = JsonInclude.Include.NON_NULL)
 class PublicKeyCredentialRequestOptionsMixin {
-
-	@JsonSerialize(using = DurationSerializer.class)
-	private final Duration timeout = null;
-
+	@JsonCreator
+	public PublicKeyCredentialRequestOptionsMixin(
+		@JsonProperty("challenge") Bytes challenge,
+		@JsonProperty("timeout") @JsonSerialize(using=DurationSerializer.class) @JsonDeserialize(using=DurationDeserializer.class) Duration timeout,
+		@JsonProperty("rpId") String rpId,
+		@JsonProperty("allowCredentials") List<PublicKeyCredentialDescriptor> allowCredentials,
+		@JsonProperty("userVerification") UserVerificationRequirement userVerification,
+		@JsonProperty("extensions") AuthenticationExtensionsClientInputs extensions
+	) {
+	}
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRpEntityMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRpEntityMixin.java
new file mode 100644
index 00000000000..4683f3f87c9
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRpEntityMixin.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
+
+/**
+ * Jackson mixin for {@link PublicKeyCredentialRpEntity}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+abstract class PublicKeyCredentialRpEntityMixin {
+        @JsonCreator
+        public PublicKeyCredentialRpEntityMixin(
+                @JsonProperty("name") String name,
+                @JsonProperty("id") String id
+        ) {
+        }
+    }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityDeserializer.java
new file mode 100644
index 00000000000..721ceb46b30
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityDeserializer.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.springframework.security.web.webauthn.api.Bytes;
+import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
+import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity.PublicKeyCredentialUserEntityBuilder;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
+
+import java.io.IOException;
+
+/**
+ * Jackson deserializer for {@link PublicKeyCredentialUserEntity}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+public class PublicKeyCredentialUserEntityDeserializer extends StdDeserializer<PublicKeyCredentialUserEntity> {
+
+    public PublicKeyCredentialUserEntityDeserializer() {
+        super(PublicKeyCredentialUserEntity.class);
+    }
+
+    @Override
+    public PublicKeyCredentialUserEntity deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		final PublicKeyCredentialUserEntityBuilder builder = ImmutablePublicKeyCredentialUserEntity.builder();
+
+        while (parser.nextToken() != JsonToken.END_OBJECT) {
+            final String fieldName = parser.currentName();
+			parser.nextToken();
+            if ("id".equals(fieldName)) {
+				builder.id(Bytes.fromBase64(parser.getText()));
+            } else if ("name".equals(fieldName)) {
+				builder.name(parser.getText());
+            } else if ("displayName".equals(fieldName)) {
+				builder.displayName(parser.getText());
+            } else {
+				throw new IOException("Unsupported field name: " + fieldName);
+            }
+        }
+
+        return builder.build();
+    }
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityMixin.java
new file mode 100644
index 00000000000..4810d5f7466
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntityMixin.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
+
+/**
+ * Jackson mixin for {@link PublicKeyCredentialUserEntity}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@JsonSerialize(using = PublicKeyCredentialUserEntitySerializer.class)
+@JsonDeserialize(using = PublicKeyCredentialUserEntityDeserializer.class)
+abstract class PublicKeyCredentialUserEntityMixin {
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntitySerializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntitySerializer.java
new file mode 100644
index 00000000000..cd42e9fafcb
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialUserEntitySerializer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
+
+import java.io.IOException;
+
+/**
+ * Jackson serializer for {@link PublicKeyCredentialUserEntity}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+public class PublicKeyCredentialUserEntitySerializer extends StdSerializer<PublicKeyCredentialUserEntity> {
+
+    public PublicKeyCredentialUserEntitySerializer() {
+        super(PublicKeyCredentialUserEntity.class);
+    }
+
+    @Override
+    public void serialize(PublicKeyCredentialUserEntity value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+        gen.writeStartObject();
+		if (value.getId() != null) {
+			gen.writeFieldName("id");
+			gen.writeString(value.getId().toBase64UrlString());
+		}
+		if (value.getName() != null) {
+			gen.writeFieldName("name");
+			gen.writeString(value.getName());
+		}
+		if (value.getDisplayName() != null) {
+			gen.writeFieldName("displayName");
+			gen.writeString(value.getDisplayName());
+		}
+        gen.writeEndObject();
+    }
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementDeserializer.java
new file mode 100644
index 00000000000..ad92b23b7de
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementDeserializer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
+
+import java.io.IOException;
+
+/**
+ * Jackson deserializer for {@link ResidentKeyRequirement}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+public class ResidentKeyRequirementDeserializer extends StdDeserializer<ResidentKeyRequirement> {
+
+    public ResidentKeyRequirementDeserializer() {
+        super(ResidentKeyRequirement.class);
+    }
+
+    @Override
+    public ResidentKeyRequirement deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		String type = parser.readValueAs(String.class);
+		return ResidentKeyRequirement.valueOf(type);
+    }
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java
index 1b910dc4e1a..f5298c7c414 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/ResidentKeyRequirementMixin.java
@@ -16,17 +16,19 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
 import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
 
 /**
  * Jackson mixin for {@link ResidentKeyRequirement}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
 @JsonSerialize(using = ResidentKeyRequirementSerializer.class)
+@JsonDeserialize(using = ResidentKeyRequirementDeserializer.class)
 abstract class ResidentKeyRequirementMixin {
 
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementDeserializer.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementDeserializer.java
new file mode 100644
index 00000000000..97c050b97d7
--- /dev/null
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementDeserializer.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
+import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
+
+import java.io.IOException;
+
+/**
+ * Jackson deserializer for {@link UserVerificationRequirement}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+@SuppressWarnings("serial")
+public class UserVerificationRequirementDeserializer extends StdDeserializer<UserVerificationRequirement> {
+
+    public UserVerificationRequirementDeserializer() {
+        super(UserVerificationRequirement.class);
+    }
+
+    @Override
+    public UserVerificationRequirement deserialize(JsonParser parser, DeserializationContext ctxt)
+			throws IOException {
+		String type = parser.readValueAs(String.class);
+		return UserVerificationRequirement.valueOf(type);
+    }
+}
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java
index ad6c1496656..cf8f5c22533 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/UserVerificationRequirementMixin.java
@@ -16,17 +16,19 @@
 
 package org.springframework.security.web.webauthn.jackson;
 
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-
 import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
 
 /**
  * Jackson mixin for {@link UserVerificationRequirement}
  *
  * @author Rob Winch
+ * @author Justin Cranford
  * @since 6.4
  */
 @JsonSerialize(using = UserVerificationRequirementSerializer.class)
+@JsonDeserialize(using = UserVerificationRequirementDeserializer.class)
 abstract class UserVerificationRequirementMixin {
 
 }
diff --git a/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java b/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java
index 97a1c8e1f46..6149dc2cbbe 100644
--- a/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java
+++ b/web/src/main/java/org/springframework/security/web/webauthn/jackson/WebauthnJackson2Module.java
@@ -34,8 +34,12 @@
 import org.springframework.security.web.webauthn.api.CredentialPropertiesOutput;
 import org.springframework.security.web.webauthn.api.PublicKeyCredential;
 import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
 import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
 import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialUserEntity;
 import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
 import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
 import org.springframework.security.web.webauthn.management.RelyingPartyPublicKey;
@@ -92,6 +96,12 @@ public void setupModule(SetupContext context) {
 		context.setMixInAnnotations(RelyingPartyPublicKey.class, RelyingPartyPublicKeyMixin.class);
 		context.setMixInAnnotations(ResidentKeyRequirement.class, ResidentKeyRequirementMixin.class);
 		context.setMixInAnnotations(UserVerificationRequirement.class, UserVerificationRequirementMixin.class);
+
+		context.setMixInAnnotations(PublicKeyCredentialUserEntity.class, PublicKeyCredentialUserEntityMixin.class);
+		context.setMixInAnnotations(PublicKeyCredentialRpEntity.class, PublicKeyCredentialRpEntityMixin.class);
+		context.setMixInAnnotations(PublicKeyCredentialParameters.class, PublicKeyCredentialParametersMixin.class);
+		context.setMixInAnnotations(PublicKeyCredentialDescriptor.class, PublicKeyCredentialDescriptorMixin.class);
+
 	}
 
 }
diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java
index bff4498ccf5..45de0658fe9 100644
--- a/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java
+++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/JacksonTests.java
@@ -21,6 +21,7 @@
 
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.skyscreamer.jsonassert.JSONAssert;
@@ -52,6 +53,7 @@ class JacksonTests {
 	void setup() {
 		this.mapper = new ObjectMapper();
 		this.mapper.registerModule(new WebauthnJackson2Module());
+		this.mapper.registerModule(new JavaTimeModule());
 	}
 
 	@Test
diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/MinPinLengthAuthenticationExtensionsClientInput.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/MinPinLengthAuthenticationExtensionsClientInput.java
new file mode 100644
index 00000000000..fd4d5a77399
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/MinPinLengthAuthenticationExtensionsClientInput.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import org.springframework.security.web.webauthn.api.AuthenticationExtensionsClientInput;
+
+/**
+ * Implements <a href=
+ * "https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-minpinlength-extension">
+ * Minimum PIN Length Extension (minPinLength)</a>.
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+record MinPinLengthAuthenticationExtensionsClientInput(Boolean getInput) implements AuthenticationExtensionsClientInput<Boolean> {
+	@Override
+	public String getExtensionId() {
+		return "minPinLength";
+	}
+}
diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsGivens.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsGivens.java
new file mode 100644
index 00000000000..5c5223af3f7
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsGivens.java
@@ -0,0 +1,113 @@
+package org.springframework.security.web.webauthn.jackson;
+
+import org.springframework.security.web.webauthn.api.AttestationConveyancePreference;
+import org.springframework.security.web.webauthn.api.AuthenticatorAttachment;
+import org.springframework.security.web.webauthn.api.AuthenticatorSelectionCriteria;
+import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
+import org.springframework.security.web.webauthn.api.Bytes;
+import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
+import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect;
+import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy;
+import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
+import org.springframework.security.web.webauthn.api.ImmutablePublicKeyCredentialUserEntity;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialParameters;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialRpEntity;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
+import org.springframework.security.web.webauthn.api.ResidentKeyRequirement;
+import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Object for {@code PublicKeyCredentialCreationOptions}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+public final class PublicKeyCredentialCreationOptionsGivens {
+	private PublicKeyCredentialCreationOptionsGivens() {}
+
+	public static PublicKeyCredentialCreationOptions create() {
+		return PublicKeyCredentialCreationOptions.builder()
+			.rp(
+				PublicKeyCredentialRpEntity.builder()
+					.id("example.com")
+					.name("Example RP")
+					.build()
+			)
+			.user(
+				ImmutablePublicKeyCredentialUserEntity.builder()
+					.name("name")
+					.id(Bytes.random())
+					.displayName("displayName")
+					.build()
+			)
+			.challenge(Bytes.random())
+			.pubKeyCredParams(
+				List.of(
+					PublicKeyCredentialParameters.EdDSA,
+					PublicKeyCredentialParameters.ES256,
+					PublicKeyCredentialParameters.ES384,
+					PublicKeyCredentialParameters.ES512,
+					PublicKeyCredentialParameters.RS256,
+					PublicKeyCredentialParameters.RS384,
+					PublicKeyCredentialParameters.RS512,
+					PublicKeyCredentialParameters.RS1
+				)
+			)
+			.timeout(Duration.ofSeconds(60))
+			.excludeCredentials(
+				List.of(
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.USB))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.NFC))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.BLE))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.SMART_CARD))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.HYBRID))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.INTERNAL))
+						.build()
+				)
+			)
+			.authenticatorSelection(
+				AuthenticatorSelectionCriteria.builder()
+					.userVerification(UserVerificationRequirement.PREFERRED)
+					.residentKey(ResidentKeyRequirement.REQUIRED)
+					.authenticatorAttachment(AuthenticatorAttachment.PLATFORM)
+					.build()
+			)
+			.attestation(AttestationConveyancePreference.DIRECT)
+			.extensions(
+				new ImmutableAuthenticationExtensionsClientInputs(
+					new CredProtectAuthenticationExtensionsClientInput(new CredProtect(ProtectionPolicy.USER_VERIFICATION_REQUIRED, true)),
+					new MinPinLengthAuthenticationExtensionsClientInput(true)
+				)
+			)
+			.build();
+	}
+}
diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsTests.java
new file mode 100644
index 00000000000..c7ff85787b7
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialCreationOptionsTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialCreationOptions;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Test Jackson serialization and deserialization of PublicKeyCredentialCreationOptions
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+class PublicKeyCredentialCreationOptionsTests {
+
+	private ObjectMapper mapper;
+
+	@BeforeEach
+	void setup() {
+		this.mapper = new ObjectMapper();
+		this.mapper.enable(SerializationFeature.INDENT_OUTPUT);
+		this.mapper.registerModule(new WebauthnJackson2Module());
+		this.mapper.registerModule(new JavaTimeModule());
+	}
+
+	@Test
+	public void testSerializeDeserialize() {
+		final PublicKeyCredentialCreationOptions given = PublicKeyCredentialCreationOptionsGivens.create();
+
+		final String serialized = assertDoesNotThrow(() -> this.mapper.writeValueAsString(given));
+		//System.out.println("serialized:\n" + serialized + "\n\n");
+
+		final PublicKeyCredentialCreationOptions deserialized = assertDoesNotThrow(() -> this.mapper.readValue(serialized, PublicKeyCredentialCreationOptions.class));
+		//System.out.println("deserialized:\n" + deserialized + "\n\n");
+
+		final String serializedAgain = assertDoesNotThrow(() -> this.mapper.writeValueAsString(deserialized));
+		//System.out.println("serializedAgain:\n" + serializedAgain + "\n\n");
+
+		assertEquals(serialized, serializedAgain);
+	}
+}
diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsGivens.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsGivens.java
new file mode 100644
index 00000000000..922694b3094
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsGivens.java
@@ -0,0 +1,75 @@
+package org.springframework.security.web.webauthn.jackson;
+
+import org.springframework.security.web.webauthn.api.AuthenticatorTransport;
+import org.springframework.security.web.webauthn.api.Bytes;
+import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput;
+import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect;
+import org.springframework.security.web.webauthn.api.CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy;
+import org.springframework.security.web.webauthn.api.ImmutableAuthenticationExtensionsClientInputs;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialDescriptor;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialType;
+import org.springframework.security.web.webauthn.api.UserVerificationRequirement;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Object for {@code PublicKeyCredentialRequestOptions}
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+public final class PublicKeyCredentialRequestOptionsGivens {
+	private PublicKeyCredentialRequestOptionsGivens() {}
+
+	public static PublicKeyCredentialRequestOptions create() {
+		return PublicKeyCredentialRequestOptions.builder()
+			.challenge(Bytes.random())
+			.timeout(Duration.ofSeconds(60))
+			.rpId("example.com")
+			.allowCredentials(
+				List.of(
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.USB))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.NFC))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.BLE))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.SMART_CARD))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.HYBRID))
+						.build(),
+					PublicKeyCredentialDescriptor.builder()
+						.id(Bytes.random())
+						.type(PublicKeyCredentialType.PUBLIC_KEY)
+						.transports(Set.of(AuthenticatorTransport.INTERNAL))
+						.build()
+				)
+			)
+			.userVerification(UserVerificationRequirement.PREFERRED)
+			.extensions(
+				new ImmutableAuthenticationExtensionsClientInputs(
+					new CredProtectAuthenticationExtensionsClientInput(new CredProtect(ProtectionPolicy.USER_VERIFICATION_REQUIRED, true)),
+					new MinPinLengthAuthenticationExtensionsClientInput(true)
+				)
+			)
+			.build();
+	}
+}
diff --git a/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsTests.java b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsTests.java
new file mode 100644
index 00000000000..38bf6970248
--- /dev/null
+++ b/web/src/test/java/org/springframework/security/web/webauthn/jackson/PublicKeyCredentialRequestOptionsTests.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * 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
+ *
+ *      https://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 org.springframework.security.web.webauthn.jackson;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.security.web.webauthn.api.PublicKeyCredentialRequestOptions;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Test Jackson serialization and deserialization of PublicKeyCredentialRequestOptions
+ *
+ * @author Justin Cranford
+ * @since 6.5
+ */
+class PublicKeyCredentialRequestOptionsTests {
+
+	private ObjectMapper mapper;
+
+	@BeforeEach
+	void setup() {
+		this.mapper = new ObjectMapper();
+		this.mapper.enable(SerializationFeature.INDENT_OUTPUT);
+		this.mapper.registerModule(new WebauthnJackson2Module());
+		this.mapper.registerModule(new JavaTimeModule());
+	}
+
+	@Test
+	public void testSerializeDeserialize() {
+		final PublicKeyCredentialRequestOptions given = PublicKeyCredentialRequestOptionsGivens.create();
+
+		final String serialized = assertDoesNotThrow(() -> this.mapper.writeValueAsString(given));
+		//System.out.println("serialized:\n" + serialized + "\n\n");
+
+		final PublicKeyCredentialRequestOptions deserialized = assertDoesNotThrow(() -> this.mapper.readValue(serialized, PublicKeyCredentialRequestOptions.class));
+		//System.out.println("deserialized:\n" + deserialized + "\n\n");
+
+		final String serializedAgain = assertDoesNotThrow(() -> this.mapper.writeValueAsString(deserialized));
+		//System.out.println("serializedAgain:\n" + serializedAgain + "\n\n");
+
+		assertEquals(serialized, serializedAgain);
+	}
+}