spring-projects/spring-security · error · IllegalArgumentException
Unsupported ProtectionPolicy
Error message
Unsupported ProtectionPolicy <policy>
What it means
The Jackson2 serializer for CredProtectAuthenticationExtensionsClientInput maps each ProtectionPolicy enum constant to its WebAuthn wire-format string; if the switch sees a policy constant it does not recognize, it throws this IllegalArgumentException. This only occurs with an enum value added in a newer webauthn4j version than this serializer supports.
Solutions
- Align webauthn4j and spring-security-webauthn versions (upgrade spring-security so its serializer knows the new enum constant, or downgrade webauthn4j to the supported version).
- Log the policy value to identify which constant is missing and add a case for it in the switch.
- If client-supplied, reject or coerce unknown policy values before building the extension input object.
Example fix
// before (dependency) <dependency><groupId>com.webauthn4j</groupId><artifactId>webauthn4j-core</artifactId><version>0.29.0.RELEASE</version></dependency> // after — match the version spring-security-webauthn was built against <dependency><groupId>com.webauthn4j</groupId><artifactId>webauthn4j-core</artifactId><version>0.28.5.RELEASE</version></dependency>
Defensive patterns
Strategy: validation
Validate before calling
ProtectionPolicy policy = input.getProtectionPolicy();
if (!(policy == ProtectionPolicy.USER_VERIFICATION_OPTIONAL
|| policy == ProtectionPolicy.USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST
|| policy == ProtectionPolicy.USER_VERIFICATION_REQUIRED)) {
throw new IllegalArgumentException("Unsupported policy before serialization: " + policy);
}
Type guard
public static boolean isSerializablePolicy(ProtectionPolicy p) {
return switch (p) {
case USER_VERIFICATION_OPTIONAL,
USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST,
USER_VERIFICATION_REQUIRED -> true;
default -> false;
};
}
Try / catch
try {
objectMapper.writeValueAsString(extensionInput);
} catch (IllegalArgumentException e) {
if (!e.getMessage().startsWith("Unsupported ProtectionPolicy")) throw e;
log.error("Unknown credProtect policy {} — check webauthn4j version alignment", e.getMessage());
throw e;
}
Prevention
- Keep webauthn4j and spring-security-webauthn versions aligned in your dependency management
- Run serialization of extension inputs in integration tests after any dependency upgrade
- Check the classpath for duplicate/mixed webauthn4j jars (mvn dependency:tree)
- Add exhaustive switch checks on enums in custom serializers so new constants fail at compile time
When it happens
Trigger: Serializing a CredProtectExtensionOutput/CredProtectAuthenticationExtensionsClientInput whose ProtectionPolicy is USER_VERIFICATION_OPTIONAL_AND_CREDENTIAL_ID_LIST (or any constant not covered by the serializer's cases), typically after upgrading webauthn4j without updating the Jackson serializer's switch.
Common situations: Dependency version mismatch: spring-security-webauthn compiled against an older webauthn4j while a newer one (with a new enum constant) is on the classpath; manually constructing a ProtectionPolicy from client extension output carrying an unexpected policy.
Related errors
- Unsupported ProtectionPolicy
- An error occurred writing the OAuth 2.0 Access Token…
- An error occurred writing the OpenID Client Registration
- Cannot find JSON Converter on the classpath. Add one…
- Credential with id already exists
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/5f080af12db23e2e.
Report an issue: GitHub.
Appendix: source
Thrown at webauthn/src/main/java/org/springframework/security/web/webauthn/jackson/CredProtectAuthenticationExtensionsClientInputJackson2Serializer.java:64
@Override
public void serialize(CredProtectAuthenticationExtensionsClientInput input, JsonGenerator jgen,
SerializerProvider provider) throws IOException {
CredProtectAuthenticationExtensionsClientInput.CredProtect credProtect = input.getInput();
String policy = toString(credProtect.getCredProtectionPolicy());
jgen.writeObjectField("credentialProtectionPolicy", policy);
jgen.writeObjectField("enforceCredentialProtectionPolicy", credProtect.isEnforceCredentialProtectionPolicy());
}
private static String toString(CredProtectAuthenticationExtensionsClientInput.CredProtect.ProtectionPolicy policy) {
switch (policy) {
case USER_VERIFICATION_OPTIONAL:
return "userVerificationOptional";
case USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST:
return "userVerificationOptionalWithCredentialIdList";
case USER_VERIFICATION_REQUIRED:
return "userVerificationRequired";
default:
throw new IllegalArgumentException("Unsupported ProtectionPolicy " + policy);
}
}
}
View on GitHub (pinned to 96852e8860)