{"record":{"id":"96cba59461b5d51c","repo":"spring-projects/spring-security","slug":"unable-to-authenticate-the-publickeycredential","errorCode":null,"errorMessage":"Unable to authenticate the PublicKeyCredential","messagePattern":"Unable to authenticate the PublicKeyCredential","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":401,"severity":"error","filePath":"webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationFilter.java","lineNumber":110,"sourceCode":"\t\tsetSecurityContextRepository(new HttpSessionSecurityContextRepository());\n\t\tsetAuthenticationFailureHandler(\n\t\t\t\tnew AuthenticationEntryPointFailureHandler(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)));\n\t\tsetAuthenticationSuccessHandler(new HttpMessageConverterAuthenticationSuccessHandler());\n\t}\n\n\t@Override\n\tpublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)\n\t\t\tthrows AuthenticationException, IOException, ServletException {\n\t\tServletServerHttpRequest httpRequest = new ServletServerHttpRequest(request);\n\t\tResolvableType resolvableType = ResolvableType.forClassWithGenerics(PublicKeyCredential.class,\n\t\t\t\tAuthenticatorAssertionResponse.class);\n\t\tPublicKeyCredential<AuthenticatorAssertionResponse> publicKeyCredential = null;\n\t\ttry {\n\t\t\tpublicKeyCredential = (PublicKeyCredential<AuthenticatorAssertionResponse>) this.converter\n\t\t\t\t.read(resolvableType, httpRequest, null);\n\t\t}\n\t\tcatch (Exception ex) {\n\t\t\tthrow new BadCredentialsException(\"Unable to authenticate the PublicKeyCredential\", ex);\n\t\t}\n\t\tPublicKeyCredentialRequestOptions requestOptions = this.requestOptionsRepository.load(request);\n\t\tif (requestOptions == null) {\n\t\t\tthrow new BadCredentialsException(\n\t\t\t\t\t\"Unable to authenticate the PublicKeyCredential. No PublicKeyCredentialRequestOptions found.\");\n\t\t}\n\t\tthis.requestOptionsRepository.save(request, response, null);\n\t\tRelyingPartyAuthenticationRequest authenticationRequest = new RelyingPartyAuthenticationRequest(requestOptions,\n\t\t\t\tpublicKeyCredential);\n\t\tWebAuthnAuthenticationRequestToken token = new WebAuthnAuthenticationRequestToken(authenticationRequest);\n\t\treturn getAuthenticationManager().authenticate(token);\n\t}\n\n\t/**\n\t * Sets the {@link GenericHttpMessageConverter} to use for writing\n\t * {@code PublicKeyCredential<AuthenticatorAssertionResponse>} to the response. The\n\t * default is @{code MappingJackson2HttpMessageConverter}\n\t * @param converter the {@link GenericHttpMessageConverter} to use. Cannot be null.","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationFilter.java#L92-L128","documentation":"WebAuthnAuthenticationFilter.attemptAuthentication() wraps any exception raised while reading the request body into a PublicKeyCredential<AuthenticatorAssertionResponse> and rethrows it as BadCredentialsException. It means the HTTP POST body was not a parseable/valid WebAuthn assertion credential (bad JSON, wrong shape, invalid fields).","triggerScenarios":"POSTing a login/assertion request to the WebAuthn endpoint whose body fails HttpMessageConverter deserialization: malformed JSON, missing required fields, wrong content type, or a body that is not a PublicKeyCredential at all.","commonSituations":"Frontend sends navigator.credentials.get() output as plain JSON without wrapping/formatting expected by the converter; wrong Content-Type header; using the registration payload shape for the authentication endpoint; API client hand-crafting assertion JSON.","solutions":["Log the wrapped cause (ex) of the BadCredentialsException to see the actual deserialization error, then fix the payload accordingly.","Ensure the client POSTs the PublicKeyCredential JSON (from navigator.credentials.get()) with Content-Type: application/json and the exact field names the converter expects (id, rawId, type, response{authenticatorData, clientDataJSON, signature, userHandle}).","Verify the configured converter (PublicKeyCredentialHttpMessageConverter) is registered and the request goes through the WebAuthn filter path, not a custom controller."],"exampleFix":"// before\nfetch('/webauthn/authenticate', { method: 'POST', body: assertion })\n// after\nfetch('/webauthn/authenticate', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ id: assertion.id, rawId: assertion.rawId, type: assertion.type,\n    response: { clientDataJSON: assertion.response.clientDataJSON,\n      authenticatorData: assertion.response.authenticatorData,\n      signature: assertion.response.signature,\n      userHandle: assertion.response.userHandle } })\n})","handlingStrategy":"try-catch","validationCode":"function isValidAssertionPayload(body) {\n  return body && typeof body === 'object' &&\n    typeof body.id === 'string' && typeof body.rawId === 'string' &&\n    body.type === 'public-key' && body.response &&\n    typeof body.response.clientDataJSON === 'string' &&\n    typeof body.response.authenticatorData === 'string' &&\n    typeof body.response.signature === 'string';\n}\n","typeGuard":"public static boolean isPublicKeyCredentialMap(Object body) {\n    return body instanceof Map<?, ?> m\n        && m.get(\"id\") instanceof String\n        && m.get(\"rawId\") instanceof String\n        && m.get(\"response\") instanceof Map<?, ?> r\n        && r.get(\"clientDataJSON\") instanceof String;\n}\n","tryCatchPattern":"try {\n    authenticationManager.authenticate(token);\n} catch (BadCredentialsException e) {\n    logger.warn(\"Invalid WebAuthn assertion\", e.getCause()); // inspect converter root cause\n    throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);\n}\n","preventionTips":["Always send navigator.credentials.get() results through PublicKeyCredential.toJSON() with Content-Type: application/json","Keep registration and assertion payload shapes on their respective endpoints","Log e.getCause() of BadCredentialsException to expose the true deserialization error","Add an integration test that POSTs the exact JSON produced by the browser"],"tags":["webauthn","authentication","http-request","java"],"backgroundTag":"authentication-required","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}