apereo/cas · error · UnauthorizedProxyingException

Service [ ] attempted to proxy, but is not allowed.

Error message

Service [{}] attempted to proxy, but is not allowed.

What it means

createProxyGrantingTicket enforces that the registered service owning the presented service ticket has a proxy policy allowing proxying. If registeredService.getProxyPolicy().isAllowedToProxy() is false, it throws UnauthorizedProxyingException (default message 'Service [{}] attempted to proxy, but is not allowed.').

Solutions

  1. Set an allowing proxyPolicy on the registered service (RegexMatchingRegisteredServiceProxyPolicy matching the proxy callback URL)
  2. Configure the proxy callback URL (pgtCallbackUrl) for the client and register it in the policy pattern
  3. Restart/refresh the services manager cache after editing the registry entry
  4. If proxying is unnecessary, switch the client to plain service-ticket validation

Example fix

// before
"proxyPolicy": { "@class": "org.apereo.cas.services.RefuseRegisteredServiceProxyPolicy" }
// after
"proxyPolicy": {
  "@class": "org.apereo.cas.services.RegexMatchingRegisteredServiceProxyPolicy",
  "pattern": "^https://myapp.example.org/cas-proxy-callback"
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Check the registered service allows proxying before requesting a PGT
RegisteredService rs = servicesManager.findServiceBy(service);
if (rs == null || rs.getProxyPolicy() == null || !rs.getProxyPolicy().isAllowedToProxy()) {
    throw new IllegalStateException("Proxying not enabled for " + service.getId());
}

Type guard

// Narrow to a proxy-capable policy
if (rs != null && rs.getProxyPolicy() != null
        && rs.getProxyPolicy().isAllowedToProxy()) {
    // safe to call grantProxyGrantingTicket
}

Try / catch

try {
    pgt = cas.grantProxyGrantingTicket(tgtId, stId, creds);
} catch (UnauthorizedProxyingException e) {
    logger.error("Proxy policy denied: {}", e.getMessage());
    // degrade to plain ST validation
}

Prevention

When it happens

Trigger: Calling grantProxyGrantingTicket(pgtTicketId, serviceTicketId, credentials) with a service ticket whose registered service has no proxy policy or a deny-all policy (e.g. RefuseRegisteredServiceProxyPolicy or missing proxyPolicy).

Common situations: Service upgraded to proxy authentication without updating its registry entry; registry entry seeded from a template with proxyPolicy unset (deny by default); admin disabled proxying in the services manager; proxy callback URL config not performed so policy remains default.

Understand the failure class

Background: "You do not have permission" / 403 Forbidden errors: authenticated but not allowed — causes and fixes across open-source libraries — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at core/cas-server-core/src/main/java/org/apereo/cas/DefaultCentralAuthenticationService.java:323

        val serviceTicket = configurationContext.getTicketRegistry().getTicket(serviceTicketId, ServiceTicket.class);
        if (serviceTicket == null || serviceTicket.isExpired()) {
            LOGGER.debug("ServiceTicket [{}] has expired or cannot be found in the ticket registry", serviceTicketId);
            throw new InvalidTicketException(serviceTicketId);
        }
        val registeredService = (CasModelRegisteredService) configurationContext.getServicesManager()
            .findServiceBy(serviceTicket.getService());

        val ctx = AuditableContext.builder()
            .serviceTicket(serviceTicket)
            .authenticationResult(authenticationResult)
            .registeredService(registeredService)
            .build();

        enforceRegisteredServiceAccess(ctx);

        if (!Objects.requireNonNull(registeredService).getProxyPolicy().isAllowedToProxy()) {
            LOGGER.warn("Service [{}] attempted to proxy, but is not allowed.", serviceTicket.getService().getId());
            throw new UnauthorizedProxyingException();
        }

        return configurationContext.getLockRepository().execute(serviceTicket.getId(),
                Unchecked.supplier(() -> {
                    val authentication = authenticationResult.getAuthentication();
                    val factory = (ProxyGrantingTicketFactory) configurationContext.getTicketFactory().get(ProxyGrantingTicket.class);
                    val proxyGrantingTicket = factory.create(serviceTicket, authentication);
                    val addedTicket = Objects.requireNonNull(configurationContext.getTicketRegistry().addTicket(proxyGrantingTicket));
                    LOGGER.debug("Generated proxy granting ticket [{}] based off of [{}]", proxyGrantingTicket, serviceTicketId);
                    if (!serviceTicket.isStateless()) {
                        configurationContext.getTicketRegistry()
                            .updateTicket(Objects.requireNonNull(serviceTicket.getTicketGrantingTicket()));
                    }
                    val clientInfo = ClientInfoHolder.getClientInfo();
                    doPublishEvent(new CasProxyGrantingTicketCreatedEvent(this, addedTicket, clientInfo));
                    return addedTicket;
                }))

View on GitHub (pinned to e7288fc434)