{"record":{"id":"87d643210c78650f","repo":"spring-projects/spring-security","slug":"invalid-request-87d643","errorCode":"invalid_request","errorMessage":"invalid_request","messagePattern":"invalid_request","errorType":"error_code","errorClass":"OAuth2AuthenticationException","httpStatus":400,"severity":"error","filePath":"oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java","lineNumber":69,"sourceCode":"\t@Override\n\tpublic @Nullable Authentication convert(HttpServletRequest request) {\n\t\tX509Certificate[] clientCertificateChain = (X509Certificate[]) request\n\t\t\t.getAttribute(\"jakarta.servlet.request.X509Certificate\");\n\t\tif (clientCertificateChain == null || clientCertificateChain.length == 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tMultiValueMap<String, String> parameters = OAuth2EndpointUtils.getFormParameters(request);\n\n\t\t// client_id (REQUIRED)\n\t\tString clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);\n\t\tif (!StringUtils.hasText(clientId)) {\n\t\t\treturn null;\n\t\t}\n\n\t\tList<String> clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID);\n\t\tif (clientIdParams == null || clientIdParams.size() != 1) {\n\t\t\tthrow new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);\n\t\t}\n\n\t\tMap<String, Object> additionalParameters = OAuth2EndpointUtils\n\t\t\t.getParametersIfMatchesAuthorizationCodeGrantRequest(request, OAuth2ParameterNames.CLIENT_ID);\n\n\t\tClientAuthenticationMethod clientAuthenticationMethod = (clientCertificateChain.length == 1)\n\t\t\t\t? ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH : ClientAuthenticationMethod.TLS_CLIENT_AUTH;\n\n\t\treturn new OAuth2ClientAuthenticationToken(clientId, clientAuthenticationMethod, clientCertificateChain,\n\t\t\t\tadditionalParameters);\n\t}\n\n}\n","sourceCodeStart":51,"sourceCodeEnd":83,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java#L51-L83","documentation":"X509ClientCertificateAuthenticationConverter (mTLS client authentication, RFC 8705) throws invalid_request when the client_id parameter is missing-empty or present more than once in a request that carries a client certificate chain. The certificate identifies the client cryptographically, but client_id must still be supplied exactly once so the server can locate the registration and compare the certificate thumbprint.","triggerScenarios":"A request presenting an X.509 client certificate whose form/query parameters omit client_id, or include client_id more than once; thrown from convert() after clientId is found non-empty but the parameter list fails the single-value check.","commonSituations":"mTLS clients configured with certificates but forgetting to add client_id to the token request body; reverse TLS-terminating proxies stripping or duplicating form parameters; clients sending client_id both in query and body; mixed setups where some endpoints expect certificate auth and others secret auth causing inconsistent clients.","solutions":["Add exactly one client_id parameter (matching the certificate's registered subject or thumbprint) to the mTLS token request.","Deduplicate client_id across query string and form body.","Verify the TLS proxy forwards the client certificate chain intact and does not mangle the request body.","Check client registration: the client must be registered with a client-authentication-method of tls_client_auth or self_signed_tls_client_auth and a matching certificate."],"exampleFix":"// before\nPOST /oauth2/token (mutual TLS)\ngrant_type=client_credentials   // client_id missing\n// after\nPOST /oauth2/token (mutual TLS)\ngrant_type=client_credentials&client_id=mtls-client","handlingStrategy":"validation","validationCode":"const p = new URLSearchParams(body);\nif (p.getAll('client_id').length !== 1 || !p.get('client_id')) {\n  throw new Error('mTLS token requests require exactly one client_id alongside the certificate');\n}\nif (!tlsSocket.getPeerCertificate()) {\n  throw new Error('client certificate not presented');\n}","typeGuard":"function isMtlsTokenRequest(req) {\n  return Boolean(req.socket.getPeerCertificate?.()?.fingerprint) &&\n    req.body.getAll('client_id').length === 1;\n}","tryCatchPattern":"try {\n  const res = await mtlsTokenRequest({ cert, key, clientId, grantType: 'client_credentials' });\n} catch (e) {\n  if (e.error === 'invalid_request') {\n    console.error('Verify client_id is present exactly once and cert matches registration');\n  }\n}","preventionTips":["Register the client with tls_client_auth (or self_signed_tls_client_auth) and configure the certificate thumbprint/subject on the server.","Always include client_id in the mTLS token request body — the certificate alone is not enough for the converter.","Ensure the TLS-terminating proxy forwards the client certificate chain and the original body unchanged.","Test the full handshake end-to-end with curl --cert/--key against the staging server before production."],"tags":["oauth2","mtls","x509","client-authentication","invalid-request"],"backgroundTag":"missing-required-argument","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}