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

  1. Add exactly one client_id parameter (matching the certificate's registered subject or thumbprint) to the mTLS token request.
  2. Deduplicate client_id across query string and form body.
  3. Verify the TLS proxy forwards the client certificate chain intact and does not mangle the request body.
  4. 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

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.