apereo/cas · error

invalid_request

invalid_request

Error message

The presentation response could not be validated

What it means

OidcVerifiableCredentialPresentationResponseEndpointController.handleResponse validates a verifiable presentation submission (vp_token, credentials, nonce, transient session ticket) inside a try block. Any Throwable during validation results in HTTP 400 with error 'invalid_request' and message 'The presentation response could not be validated'; the transient session ticket is only deleted on success.

Solutions

  1. Re-run the authorization/presentation flow to get a fresh nonce and transient session ticket, then submit the response once.
  2. Verify the holder's presentation is signed with a supported algorithm and the credentials match the requested types.
  3. Ensure the nonce from the earlier response is included unchanged in the presentation.
  4. Inspect CAS logs (LoggingUtils warns with the underlying throwable) to identify the exact validation failure.

Example fix

// before: reusing a consumed ticket/nonce
POST presentation-response with old vp_token + old nonce
// after: restart flow
1) obtain new nonce + transient ticket
2) build fresh vp_token including that nonce
3) POST once
Defensive patterns

Strategy: try-catch

Validate before calling

// Client-side sanity checks before submitting the presentation
if (vpToken == null || nonce == null || transientTicketId == null) {
    throw new IllegalArgumentException("vp_token, nonce and transient ticket are required");
}

Try / catch

try {
    Response resp = submitPresentation(vpToken, credentials, nonce, ticketId);
    if (resp.status() == 400 && body.contains("could not be validated")) {
        restartFlow(); // new nonce + ticket, fresh presentation
    }
} catch (IOException e) {
    LOGGER.warn("Presentation submission failed", e);
}

Prevention

When it happens

Trigger: Submitting a presentation response whose vp_token signature/proof fails verification, credentials don't match the requested types, the nonce doesn't match the issued one, or the transient session ticket is missing/expired — any exception in validatePresentation.

Common situations: Wallet replaying an old nonce; clock skew invalidating credential validity; unsupported proof/algorithm in the holder's presentation; user retrying after the transient ticket was already consumed or expired.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/25bf62839f6e3a1f. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/vc/presentation/OidcVerifiableCredentialPresentationResponseEndpointController.java:111

        @RequestParam final String state) {

        try {
            require(!vpToken.isBlank() && !state.isBlank(), "Presentation response parameters cannot be blank");
            val transientSessionTicket = configurationContext.getTicketRegistry().getTicket(state, TransientSessionTicket.class);
            require(transientSessionTicket != null && !transientSessionTicket.isExpired(), "Presentation transaction is invalid");
            require(state.equals(transientSessionTicket.getPropertyAsString("state")), "Presentation state does not match");

            val nonce = transientSessionTicket.getPropertyAsString("nonce");
            require(nonce != null && !nonce.isBlank(), "Presentation transaction has no nonce");
            val credentials = (List<CredentialRequest>) transientSessionTicket.getProperty("credentials", List.class);
            require(credentials != null && !credentials.isEmpty(), "Presentation transaction has no credential query");

            validatePresentation(vpToken, credentials, nonce, transientSessionTicket);
            configurationContext.getTicketRegistry().deleteTicket(transientSessionTicket);
            
            return buildResponse(HttpStatus.OK, Map.of("status", "verified"));
        } catch (final Throwable throwable) {
            LoggingUtils.warn(LOGGER, throwable);
            return buildResponse(HttpStatus.BAD_REQUEST,
                OAuth20Utils.getErrorResponseBody(OAuth20Constants.INVALID_REQUEST,
                    "The presentation response could not be validated")
            );
        }
    }

    private void validatePresentation(final String vpToken,
                                      final List<CredentialRequest> credentials,
                                      final String nonce,
                                      final TransientSessionTicket transientSessionTicket) throws Throwable {
        val credentialQueries = new LinkedHashMap<String, CredentialRequest>();
        for (val credential : credentials) {
            require(credential != null && credential.getId() != null && !credential.getId().isBlank(),
                "Credential query id is invalid");
            require(OidcVerifiableCredentialConfigurationProperties.CredentialConfigurationFormats.DC_SD_JWT
                .getValue().equals(credential.getFormat()), "Credential query format is not supported");
            require(credential.getVctValues() != null && !credential.getVctValues().isEmpty()

View on GitHub (pinned to e7288fc434)