apereo/cas · error
invalid_request
invalid_request
Error message
CAS cannot accept the request given the issuer is invalid.
What it means
OidcVerifiableCredentialEndpointController.verifyRequest validates the request's issuer against the configured issuer service for the VC credential URL path. When the Host/issuer of the incoming request does not match the configured OIDC issuer, the endpoint returns HTTP 400 with error 'invalid_request' and description 'Invalid issuer'.
Solutions
- Access the endpoint using the exact URL that matches the configured cas.authn.oidc.issuer.
- Configure the proxy/load balancer to preserve Host and X-Forwarded-* headers.
- Update the issuer configuration to include the scheme/host/path clients actually use.
Example fix
// before (request through wrong host) curl https://localhost:8443/cas/oidc/vc/credential // after # with issuer=https://sso.example.org/cas/oidc curl https://sso.example.org/cas/oidc/vc/credential -H "Authorization: Bearer ..."
Defensive patterns
Strategy: validation
Validate before calling
// Client-side: ensure request URL starts with the configured issuer
String issuer = "https://sso.example.org/cas/oidc";
if (!requestUrl.startsWith(issuer)) {
throw new IllegalArgumentException("Request must use the configured issuer URL " + issuer);
} Try / catch
// Detect the 400 invalid_request body
Response resp = client.send(req);
if (resp.status() == 400 && body.contains("Invalid issuer")) {
throw new IllegalStateException("Issuer mismatch: use " + configuredIssuer);
} Prevention
- Always derive endpoint URLs from the issuer metadata document, never hardcode hosts
- Preserve Host/X-Forwarded headers at proxies
- Use the same hostname in dev tooling as configured in the issuer
When it happens
Trigger: Calling the verifiable-credential issuance endpoint with a request whose host/origin does not match cas.authn.oidc.issuer (issuerService.validateIssuer fails for OidcConstants.VC_CREDENTIAL_URL).
Common situations: Proxying behind a gateway that rewrites the Host header; accessing the server via localhost/IP instead of the configured issuer hostname; HTTP vs HTTPS mismatch; issuer configured with trailing path differences.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- invalid_request
- invalid_request
- invalid_request
- screen.oidc.issuer.invalid
- Missing required principal attribute for claim
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/8158d023135b8f6a.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/web/OidcVerifiableCredentialEndpointController.java:165
nonces.add(issuedCredential.nonce());
responses.add(OidcVerifiableCredentialResponse
.builder()
.format(issuedCredential.format().getValue())
.credential(issuedCredential.credential())
.build());
}
nonces.forEach(oidcVerifiableCredentialNonceService::remove);
return responses.size() == 1
? ResponseEntity.ok(responses.getFirst())
: ResponseEntity.ok(Map.of("credential_responses", responses));
}
protected Couplet<@Nullable OAuth20AccessToken, @Nullable ResponseEntity> verifyRequest(
final HttpServletRequest httpRequest,
final HttpServletResponse httpResponse) {
val webContext = new JEEContext(httpRequest, httpResponse);
if (!getConfigurationContext().getIssuerService().validateIssuer(webContext, List.of(OidcConstants.VC_CREDENTIAL_URL))) {
LOGGER.warn("CAS cannot accept the request given the issuer is invalid.");
val body = OAuth20Utils.getErrorResponseBody(OAuth20Constants.INVALID_REQUEST, "Invalid issuer");
return Couplet.right(ResponseEntity.badRequest().body(body));
}
val decodedAccessTokenId = getAccessTokenFromRequest(httpRequest).getValue();
val decodedToken = getConfigurationContext().getTicketRegistry().getTicket(decodedAccessTokenId, OAuth20AccessToken.class);
if (!validateAccessToken(decodedToken)) {
LOGGER.warn("The access token is invalid, expired, has an invalid grant type or no authorization details.");
return Couplet.right(ResponseEntity.badRequest()
.body(OAuth20Utils.getErrorResponseBody(OAuth20Constants.ERROR, "Invalid access token")));
}
return Couplet.left(decodedToken);
}
protected boolean validateAccessToken(@Nullable final OAuth20AccessToken accessToken) {
return accessToken != null && !accessToken.isExpired()
&& (accessToken.getGrantType() == OAuth20GrantTypes.PRE_AUTHORIZED_CODE || accessToken.hasAuthorizationDetails());
}View on GitHub (pinned to e7288fc434)