apereo/cas · error
invalid_request
invalid_request
Error message
Missing required parameter: [client_id]
What it means
CAS's OAuth20 authorization-request validator requires a client_id parameter. verifyRegisteredServiceByClientId throws/sets an invalid_request error when the clientId resolved from the request is blank, meaning the request omitted the parameter or it was empty. No registered OAuth service lookup is attempted in that case.
Solutions
- Include a non-empty client_id parameter in the authorization URL
- Verify the application's redirect construction actually appends client_id
- Confirm the registered service exists in CAS and use its exact client_id value
- If a proxy/gateway strips query parameters, fix the forwarding configuration
Example fix
// before https://cas.example.org/cas/oauth2.0/authorize?response_type=code&redirect_uri=https://app/cb // after https://cas.example.org/cas/oauth2.0/authorize?response_type=code&client_id=MYCLIENT&redirect_uri=https://app/cb
Defensive patterns
Strategy: validation
Validate before calling
const url = new URL(authorizeUrl);
if (!url.searchParams.get('client_id')) {
throw new Error('client_id is required before calling /oauth2.0/authorize');
} Type guard
const clientId = url.searchParams.get('client_id');
if (typeof clientId !== 'string' || clientId.trim() === '') {
throw new Error('missing client_id');
} Try / catch
try {
const res = await fetch(authorizeUrl, { redirect: 'manual' });
const body = await res.text();
if (body.includes('Missing required parameter: [client_id]')) {
throw new Error('Authorization request is missing client_id');
}
} catch (e) { /* handle missing-parameter case */ } Prevention
- Always build authorization URLs from a helper that requires client_id and redirect_uri
- Store client_id in environment config and fail fast at startup if absent
- Test the authorize URL manually in a browser before wiring the client
- Watch for proxies or frameworks that rewrite/drop query parameters
- Use the exact client_id value from the registered service definition
When it happens
Trigger: An OAuth2/OIDC authorization request (/oauth2.0/authorize) is made without a client_id query parameter, or with an empty value, so getClientIdFromRequest resolves to StringUtils.EMPTY and StringUtils.isBlank(clientId) is true.
Common situations: Application integrating CAS OAuth forgot to send client_id; client-side JS drops the query param on redirect; misconfigured redirect URL in the relying application; parameter name misspelled (clientid vs client_id).
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
- missing_access_token
- Denied
- Cannot authorize principal
- Unauthorized account removal attempt
- Unknown authorization header type
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/3088bcbb8cdfb246.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/authorization/BaseOAuth20AuthorizationRequestValidator.java:85
val responseType = getResponseTypeFromRequest(context);
return verifyResponseType(context, responseType);
}
protected String getResponseTypeFromRequest(final WebContext context) {
return requestParameterResolver.resolveRequestParameter(context, OAuth20Constants.RESPONSE_TYPE).orElse(StringUtils.EMPTY);
}
protected String getRedirectUriFromRequest(final WebContext context) {
return requestParameterResolver.resolveRequestParameter(context, OAuth20Constants.REDIRECT_URI).orElse(StringUtils.EMPTY);
}
protected String getClientIdFromRequest(final WebContext context) {
return requestParameterResolver.resolveRequestParameter(context, OAuth20Constants.CLIENT_ID).orElse(StringUtils.EMPTY);
}
protected OAuthRegisteredService verifyRegisteredServiceByClientId(final WebContext context, final String clientId) throws Throwable {
if (StringUtils.isBlank(clientId)) {
LOGGER.warn("Missing required parameter [{}]", OAuth20Constants.CLIENT_ID);
setErrorDetails(context, OAuth20Constants.INVALID_REQUEST, String.format("Missing required parameter: [%s]", OAuth20Constants.CLIENT_ID), false);
return null;
}
LOGGER.debug("Locating registered service for client id [{}]", clientId);
val registeredService = getRegisteredServiceByClientId(clientId);
val audit = AuditableContext.builder()
.registeredService(registeredService)
.build();
val accessResult = registeredServiceAccessStrategyEnforcer.execute(audit);
if (accessResult.isExecutionFailure()) {
LOGGER.warn("Registered service [{}] is not found or is not authorized for access.",
ObjectUtils.getIfNull(registeredService, clientId));
setErrorDetails(context, OAuth20Constants.INVALID_REQUEST,
String.format("Service [%s] is not found or is not authorized for access", clientId), false);
return null;
}View on GitHub (pinned to e7288fc434)