spring-projects/spring-security · error · OAuth2AuthenticationException
invalid_request
invalid_request
Error message
invalid_request
What it means
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.
Source
Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/X509ClientCertificateAuthenticationConverter.java:69
@Override
public @Nullable Authentication convert(HttpServletRequest request) {
X509Certificate[] clientCertificateChain = (X509Certificate[]) request
.getAttribute("jakarta.servlet.request.X509Certificate");
if (clientCertificateChain == null || clientCertificateChain.length == 0) {
return null;
}
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getFormParameters(request);
// client_id (REQUIRED)
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
if (!StringUtils.hasText(clientId)) {
return null;
}
List<String> clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID);
if (clientIdParams == null || clientIdParams.size() != 1) {
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
}
Map<String, Object> additionalParameters = OAuth2EndpointUtils
.getParametersIfMatchesAuthorizationCodeGrantRequest(request, OAuth2ParameterNames.CLIENT_ID);
ClientAuthenticationMethod clientAuthenticationMethod = (clientCertificateChain.length == 1)
? ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH : ClientAuthenticationMethod.TLS_CLIENT_AUTH;
return new OAuth2ClientAuthenticationToken(clientId, clientAuthenticationMethod, clientCertificateChain,
additionalParameters);
}
}
View on GitHub (pinned to 96852e8860)
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.
Example fix
// before POST /oauth2/token (mutual TLS) grant_type=client_credentials // client_id missing // after POST /oauth2/token (mutual TLS) grant_type=client_credentials&client_id=mtls-client
Defensive patterns
Strategy: validation
Validate before calling
const p = new URLSearchParams(body);
if (p.getAll('client_id').length !== 1 || !p.get('client_id')) {
throw new Error('mTLS token requests require exactly one client_id alongside the certificate');
}
if (!tlsSocket.getPeerCertificate()) {
throw new Error('client certificate not presented');
} Type guard
function isMtlsTokenRequest(req) {
return Boolean(req.socket.getPeerCertificate?.()?.fingerprint) &&
req.body.getAll('client_id').length === 1;
} Try / catch
try {
const res = await mtlsTokenRequest({ cert, key, clientId, grantType: 'client_credentials' });
} catch (e) {
if (e.error === 'invalid_request') {
console.error('Verify client_id is present exactly once and cert matches registration');
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/87d643210c78650f.
Report an issue: GitHub.