spring-projects/spring-security · error · OAuth2AuthenticationException

invalid_request

invalid_request

Error message

invalid_request

What it means

PublicClientAuthenticationConverter (used for PKCE public clients) throws invalid_request when the client_id parameter is absent, empty, or appears more than once in the authorization/token request. Public clients authenticate via client_id plus code_verifier rather than a secret, so client_id is mandatory and must be a single value.

Source

Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/authentication/PublicClientAuthenticationConverter.java:65

 * @see <a target="_blank" href="https://tools.ietf.org/html/rfc7636">Proof Key for Code
 * Exchange by OAuth Public Clients</a>
 */
public final class PublicClientAuthenticationConverter implements AuthenticationConverter {

	@Override
	public @Nullable Authentication convert(HttpServletRequest request) {
		if (!OAuth2EndpointUtils.matchesPkceTokenRequest(request)) {
			return null;
		}

		MultiValueMap<String, String> parameters = "GET".equals(request.getMethod())
				? OAuth2EndpointUtils.getQueryParameters(request) : OAuth2EndpointUtils.getFormParameters(request);

		// client_id (REQUIRED for public clients)
		String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
		List<String> clientIdParams = parameters.get(OAuth2ParameterNames.CLIENT_ID);
		if (!StringUtils.hasText(clientId) || clientIdParams == null || clientIdParams.size() != 1) {
			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
		}

		// code_verifier (REQUIRED)
		List<String> codeVerifierParams = parameters.get(PkceParameterNames.CODE_VERIFIER);
		if (codeVerifierParams == null || codeVerifierParams.size() != 1) {
			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
		}

		parameters.remove(OAuth2ParameterNames.CLIENT_ID);

		Map<String, Object> additionalParameters = new HashMap<>();
		parameters.forEach((key, value) -> additionalParameters.put(key,
				(value.size() == 1) ? value.get(0) : value.toArray(new String[0])));

		return new OAuth2ClientAuthenticationToken(clientId, ClientAuthenticationMethod.NONE, null,
				additionalParameters);
	}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Include exactly one client_id parameter matching a registered public client in the request.
  2. Remove duplicate client_id occurrences (check both query string and form body).
  3. Verify the converter is appropriate: if your client is confidential, use ClientSecretAuthenticationConverter/basic auth instead of the public-client path.
  4. Log the request parameters before hitting the endpoint to confirm client_id presence and count.

Example fix

// before
POST /oauth2/token
grant_type=authorization_code&code=abc&code_verifier=xyz   // no client_id
// after
POST /oauth2/token
grant_type=authorization_code&code=abc&code_verifier=xyz&client_id=my-public-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('public client requests require exactly one client_id');
}

Type guard

function isPublicClientRequest(params) {
  const ids = params.getAll('client_id');
  return ids.length === 1 && ids[0].length > 0 && params.getAll('code_verifier').length === 1;
}

Try / catch

try {
  const res = await tokenRequest({ clientId, code, codeVerifier });
} catch (e) {
  if (e.error === 'invalid_request') {
    console.error('Check client_id and code_verifier are present exactly once');
  }
}

Prevention

When it happens

Trigger: A PKCE request (matched by presence of code_verifier path) without client_id, with client_id= (empty), or with client_id sent twice; thrown from convert() at the first validation check.

Common situations: SPA/mobile clients omitting client_id from the token exchange after redirect; authorization servers configured with PublicClientAuthenticationConverter while the client sends credentials via Basic auth instead; duplicated client_id when both query and body carry it; IDE-generated curl examples missing the parameter.

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/8cb6b2dbbf50b37b. Report an issue: GitHub.