{"record":{"id":"0b036d8c778535b7","repo":"spring-projects/spring-security","slug":"unable-to-authenticate-the-publickeycredential-no","errorCode":null,"errorMessage":"Unable to authenticate the PublicKeyCredential. No PublicKeyCredentialRequestOptions found.","messagePattern":"Unable to authenticate the PublicKeyCredential\\. No PublicKeyCredentialRequestOptions found\\.","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":401,"severity":"error","filePath":"webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationFilter.java","lineNumber":114,"sourceCode":"\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.\n\t * @deprecated use {@link #setConverter(SmartHttpMessageConverter)}\n\t */\n\t@Deprecated(forRemoval = true, since = \"7.0\")\n\tpublic void setConverter(GenericHttpMessageConverter<Object> converter) {","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/webauthn/src/main/java/org/springframework/security/web/webauthn/authentication/WebAuthnAuthenticationFilter.java#L96-L132","documentation":"WebAuthnAuthenticationFilter.attemptAuthentication() loads the stored PublicKeyCredentialRequestOptions for the current request before validating the credential; if the repository returns null it throws this BadCredentialsException. It means the server has no record of a challenge for this authentication attempt.","triggerScenarios":"POSTing an assertion when requestOptionsRepository.load(request) returns null: the challenge was never created, already consumed (saved as null after a prior attempt), expired/cleared from the repository, or the request hits a different server/session than the one that issued the challenge.","commonSituations":"Application restarted between challenge issuance and assertion (in-memory repository lost); load-balanced deployment without sticky sessions/shared repository; client retries the POST and the filter's save(request, response, null) already cleared the options; missing step that creates the options (WebAuthnRequestsURLEndpoint) beforehand.","solutions":["Ensure the client first calls the endpoint that generates and stores PublicKeyCredentialRequestOptions, then immediately POSTs the assertion.","Use a shared/persistent PublicKeyCredentialRequestOptionsRepository (e.g. HTTP-session or database-backed) across nodes instead of per-instance in-memory storage.","Check the request carries the identifying cookie/session data so load() can find the stored options; re-obtain a fresh challenge after every consumed attempt."],"exampleFix":"// before\n@Bean\nPublicKeyCredentialRequestOptionsRepository requestOptionsRepository() {\n    return new InMemoryPublicKeyCredentialRequestOptionsRepository(); // lost on restart / across nodes\n}\n// after\n@Bean\nPublicKeyCredentialRequestOptionsRepository requestOptionsRepository() {\n    return new HttpSessionPublicKeyCredentialRequestOptionsRepository(); // shared per user session\n}","handlingStrategy":"try-catch","validationCode":"// client: fetch assertion options first and fail fast if unavailable\nconst opts = await fetch('/webauthn/options', { credentials: 'include' });\nif (!opts.ok) throw new Error('No challenge issued — cannot authenticate');\n","typeGuard":null,"tryCatchPattern":"try {\n    authenticationManager.authenticate(token);\n} catch (BadCredentialsException e) {\n    if (e.getMessage().contains(\"No PublicKeyCredentialRequestOptions\")) {\n        throw new ResponseStatusException(HttpStatus.CONFLICT, \"Challenge expired; request a new one\");\n    }\n    throw e;\n}\n","preventionTips":["Always issue and store PublicKeyCredentialRequestOptions before accepting assertions","Use HttpSession-backed or persistent options repositories in clustered deployments","Remember each challenge is single-use: the filter nulls it after an attempt — always fetch a fresh one before retrying","Ensure cookies/session identifiers travel with both the options request and the assertion POST"],"tags":["webauthn","authentication","state-management","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"}