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
- Include exactly one client_id parameter matching a registered public client in the request.
- Remove duplicate client_id occurrences (check both query string and form body).
- Verify the converter is appropriate: if your client is confidential, use ClientSecretAuthenticationConverter/basic auth instead of the public-client path.
- 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
- Store the registered client_id in a single config value and always append it to token/authorize requests.
- Check both query string and body for accidental duplicates of client_id.
- If the client has a secret, do not route it through the public-client converter; use basic/secret authentication instead.
- Add a request-interceptor test asserting client_id appears exactly once.
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
- invalid_request
- invalid_request
- OAuth 2.0 Token Introspection Parameter: ${parameterName}
- OAuth 2.0 Token Revocation Parameter: ${parameterName}
- invalid_request
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/8cb6b2dbbf50b37b.
Report an issue: GitHub.