apereo/cas · error · FailedLoginException

cannot be authorized

Error message

<callbackUrl> cannot be authorized

What it means

ProxyAuthenticationHandler.authenticate throws FailedLoginException when the service's ProxyPolicy refuses to authorize the PGT callback URL presented by the HttpBasedServiceCredential. Proxy authentication (proxy-granting tickets) requires the callback URL to pass the service's proxy policy (e.g. RegisteredServiceProxyPolicy regex), otherwise the proxy credential is rejected before any HTTP validation.

Solutions

  1. Update the registered service's proxyPolicy (e.g. RegexMatchingRegisteredServiceProxyPolicy) pattern to include the callback URL.
  2. Ensure the callback URL scheme/host matches the policy exactly (https vs http, ports, query strings).
  3. If proxying is intended, confirm the service definition allows proxy authentication and the access strategy permits it.
  4. Change the client application's pgtUrl to one already authorized by the proxy policy.
  5. Check the WARN log naming the service and callback URL to see the exact mismatch.

Example fix

// before (service JSON)
"proxyPolicy": { "@class": "org.apereo.cas.services.RefuseRegisteredServiceProxyPolicy" }
// after
"proxyPolicy": { "@class": "org.apereo.cas.services.RegexMatchingRegisteredServiceProxyPolicy", "pattern": "^https://app\\.example\\.org/.*" }
Defensive patterns

Strategy: validation

Validate before calling

// before requesting a PGT, check the policy client-side if you know the service def
boolean allowed = registeredService.getProxyPolicy()
    .isAllowedProxyCallbackUrl(registeredService, callbackUrl);
if (!allowed) { throw new IllegalArgumentException("Callback URL not authorized: " + callbackUrl); }

Try / catch

try {
    return handler.authenticate(credential, service);
} catch (FailedLoginException e) {
    if (e.getMessage() != null && e.getMessage().endsWith("cannot be authorized")) {
        LOGGER.error("Proxy callback URL [{}] rejected by service proxy policy", ((HttpBasedServiceCredential) credential).getCallbackUrl(), e);
    }
    throw e;
}

Prevention

When it happens

Trigger: A PGT callback credential (pgtUrl) is submitted and httpCredential.getService().getProxyPolicy().isAllowedProxyCallbackUrl(...) returns false — the callback URL doesn't match the registered service's proxy pattern or the proxy policy denies all callbacks.

Common situations: Registered service has proxying disabled or its proxy policy regex doesn't match the callback host/URL; callback URL uses http while the pattern allows only https; service registry entry updated and the old proxy pattern no longer matches; client app changed its pgtUrl after deployment.

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


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

Appendix: source

Thrown at core/cas-server-core-authentication-api/src/main/java/org/apereo/cas/authentication/handler/support/ProxyAuthenticationHandler.java:45

@Slf4j
public class ProxyAuthenticationHandler extends AbstractAuthenticationHandler {
    private final HttpClient httpClient;

    public ProxyAuthenticationHandler(final String name,
                                      final PrincipalFactory principalFactory,
                                      final Integer order, final HttpClient httpClient) {
        super(name, principalFactory, order);
        this.httpClient = httpClient;
    }

    @Override
    public AuthenticationHandlerExecutionResult authenticate(final Credential credential, final Service service) throws Throwable {
        val httpCredential = (HttpBasedServiceCredential) credential;
        if (!httpCredential.getService().getProxyPolicy()
            .isAllowedProxyCallbackUrl(httpCredential.getService(), httpCredential.getCallbackUrl())) {
            LOGGER.warn("Proxy policy for service [{}] cannot authorize the requested callback url [{}].",
                httpCredential.getService(), httpCredential.getCallbackUrl());
            throw new FailedLoginException(httpCredential.getCallbackUrl() + " cannot be authorized");
        }

        LOGGER.debug("Attempting to authenticate [{}]", httpCredential);
        val callbackUrl = httpCredential.getCallbackUrl();
        if (!httpClient.isValidEndPoint(callbackUrl)) {
            throw new FailedLoginException(callbackUrl.toExternalForm() + " sent an unacceptable response status code");
        }
        val principalId = httpCredential.getCredentialMetadata().getId();
        val proxyPrincipal = principalFactory.createPrincipal(principalId);
        return new DefaultAuthenticationHandlerExecutionResult(this, httpCredential, proxyPrincipal);
    }

    @Override
    public boolean supports(final Credential credential) {
        return credential instanceof HttpBasedServiceCredential;
    }

    @Override

View on GitHub (pinned to e7288fc434)