apereo/cas · error · SAMLException

Request is not signed but should be

Error message

Request is not signed but should be

What it means

During SP-initiated AuthnRequest validation the IdP checks whether the request message is signed. If the IdP metadata says AuthnRequests must be signed and the SP has not opted out of signature validation, an unsigned request is rejected with SAMLException.

Solutions

  1. Configure the SP to sign its AuthnRequests (sign redirect binding with sigAlg, or use POST binding signed).
  2. Set cas.authn.saml.idp.webflow... skip validation for the service: registeredService.setSkipValidatingAuthnRequest(true) if policy allows unsigned requests.
  3. Correct the SP metadata so isAuthnRequestsSigned reflects the SP's actual behavior.
  4. Verify the signature arrived but failed binding-level detection — check the SigAlg/Signature query parameters are intact (no proxy stripping).

Example fix

// before
service.setSkipValidatingAuthnRequest(false); // SP sends unsigned requests
// after
service.setSkipValidatingAuthnRequest(true); // or configure SP to sign AuthnRequests
Defensive patterns

Strategy: try-catch

Validate before calling

val signed = SAMLBindingSupport.isMessageSigningContext(ctx) || request.getParameter("Signature") != null;
val mustSign = adaptor.isAuthnRequestsSigned() && !registeredService.isSkipValidatingAuthnRequest();
if (mustSign && !signed) LOGGER.warn("Service [{}] will reject this unsigned AuthnRequest", registeredService.getName());

Type guard

boolean willPassSignatureCheck(ctx, adaptor, service) {
    return SAMLBindingSupport.isMessageSigned(ctx) || !adaptor.isAuthnRequestsSigned() || service.isSkipValidatingAuthnRequest();
}

Try / catch

try {
    response = idpController.handleRequest(request, response);
} catch (SAMLException e) {
    showSpConfigurationErrorPage("Sign your AuthnRequests or ask the IdP admin to relax the policy");
}

Prevention

When it happens

Trigger: verifyAuthenticationContextSignature sees SAMLBindingSupport.isMessageSigned(ctx)==false while adaptor.isAuthnRequestsSigned() is true and registeredService.isSkipValidatingAuthnRequest() is false — i.e. an unsigned AuthnRequest (e.g. via redirect binding without sigAlg) arrives for a service requiring signed requests.

Common situations: SP sends unsigned redirect-binding AuthnRequests while IdP metadata flag WantAuthnRequestsSigned/authnRequestsSigned is enabled; SP never signs requests; service registered with wrong metadata lacking the signed-requests flag.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-saml-idp-web/src/main/java/org/apereo/cas/support/saml/web/idp/profile/AbstractSamlIdPProfileHandlerController.java:440

    protected void verifyAuthenticationContextSignature(final Pair<? extends SignableSAMLObject, MessageContext> authenticationContext,
                                                        final HttpServletRequest request, final RequestAbstractType authnRequest,
                                                        final SamlRegisteredServiceMetadataAdaptor adaptor,
                                                        final SamlRegisteredService registeredService) throws Throwable {
        val ctx = authenticationContext.getValue();
        verifyAuthenticationContextSignature(ctx, request, authnRequest, adaptor, registeredService);
    }

    protected void verifyAuthenticationContextSignature(final MessageContext ctx,
                                                        final HttpServletRequest request,
                                                        final RequestAbstractType authnRequest,
                                                        final SamlRegisteredServiceMetadataAdaptor adaptor,
                                                        final SamlRegisteredService registeredService) throws Throwable {
        if (!SAMLBindingSupport.isMessageSigned(ctx)) {
            LOGGER.trace("The authentication context is not signed");
            if (adaptor.isAuthnRequestsSigned() && !registeredService.isSkipValidatingAuthnRequest()) {
                LOGGER.error("Metadata for [{}] says authentication requests are signed, yet request is not", adaptor.getEntityId());
                throw new SAMLException("Request is not signed but should be");
            }
            LOGGER.trace("Request is not signed or validation is skipped, so there is no need to verify its signature.");
        } else if (adaptor.isAuthnRequestsSigned() && !registeredService.isSkipValidatingAuthnRequest()) {
            LOGGER.trace("The authentication context is signed; Proceeding to validate signatures...");
            configurationContext.getSamlObjectSignatureValidator().verifySamlProfileRequest(authnRequest, adaptor, request, ctx);
        }
    }

    protected Pair<SamlRegisteredService, SamlRegisteredServiceMetadataAdaptor> getRegisteredServiceAndFacade(
        final AuthnRequest authnRequest, final HttpServletRequest httpServletRequest) {
        val issuer = SamlIdPUtils.getIssuerFromSamlObject(authnRequest);
        LOGGER.debug("Located issuer [{}] from authentication context", issuer);

        val registeredService = verifySamlRegisteredService(issuer, httpServletRequest);

        LOGGER.debug("Located SAML metadata for [{}]", registeredService.getServiceId());
        val adaptor = getSamlMetadataFacadeFor(registeredService, authnRequest);

View on GitHub (pinned to e7288fc434)