apereo/cas · error

access_denied

access_denied

Error message

Service [{}] requests authentication

What it means

The revocation endpoint was called for an OAuth registered service that requires an authenticated user profile, but the request carries no authenticated profile in the profile manager. Because the service is flagged as needing authentication, CAS refuses the revocation with OAuth 'access_denied'.

Solutions

  1. Authenticate the user via CAS (SSO session) before invoking the revocation endpoint so a profile exists
  2. If the revocation is intended to be client-only, adjust the service definition so it does not require authentication
  3. Verify session cookies are forwarded by the client/proxy and the session is not expired
  4. Check OAuth20Utils.doesServiceNeedAuthentication logic and the service definition's access settings to match the intended flow

Example fix

// before: service definition forces authentication for a headless client
{
  "@class": "org.apereo.cas.support.oauth.services.OAuthRegisteredService",
  "clientId": "myClient",
  "serviceId": "^https://app.example.org/.*"
}
// after: call revocation within an authenticated session, or relax the requirement
// (login first at /cas/login, then POST /cas/oauth2.0/revoke with session cookies)
Defensive patterns

Strategy: validation

Validate before calling

// ensure an authenticated profile exists before calling revocation
async function canRevoke(profileManager) {
  const profile = await profileManager.getProfile();
  return profile != null && profile.isDefined;
}
if (!(await canRevoke(profileManager))) {
  await casLogin(); // establish SSO session first
}

Type guard

function isAuthenticated(profile) {
  return profile != null && typeof profile.id === 'string' && profile.id.length > 0;
}

Try / catch

const res = await post(revokeUrl, form, { withCredentials: true });
if (res.status === 401 && res.data?.error === 'access_denied') {
  // no authenticated profile: redirect to CAS login, then retry revocation
  await casLogin();
  return post(revokeUrl, form, { withCredentials: true });
}

Prevention

When it happens

Trigger: Calling POST /oauth2.0/revoke (or related revocation route) for a service whose doesServiceNeedAuthentication() is true while the caller's session has no user profile (no prior CAS login / no cookie establishing the profile).

Common situations: Machine-to-machine revocation calls that never went through a user login; expired or missing session cookies; a service definition (bypassApprovalWidget/authentication requirements or service policy) that marks it as needing authentication while clients call the endpoint headlessly; direct API usage from backend scripts.

Understand the failure class

Related errors


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

Appendix: source

Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20RevocationEndpointController.java:73

    public ModelAndView handleRequest(final HttpServletRequest request,
                                      final HttpServletResponse response) throws Throwable {
        val context = new JEEContext(request, response);

        if (!verifyRevocationRequest(context)) {
            LOGGER.error("Revocation request verification failed. Request is missing required parameters");
            return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);
        }

        val manager = new ProfileManager(context, getConfigurationContext().getSessionStore());
        val callContext = new CallContext(context, getConfigurationContext().getSessionStore());
        val clientId = getConfigurationContext().getRequestParameterResolver()
            .resolveClientIdAndClientSecret(callContext).getLeft();
        val registeredService = getRegisteredServiceByClientId(clientId);
        RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(registeredService);
        
        if (OAuth20Utils.doesServiceNeedAuthentication(Objects.requireNonNull(registeredService))) {
            if (manager.getProfile().isEmpty()) {
                LOGGER.warn("Service [{}] requests authentication", clientId);
                return OAuth20Utils.writeError(response, OAuth20Constants.ACCESS_DENIED);
            }
        } else {
            val service = getConfigurationContext().getWebApplicationServiceServiceFactory()
                .createService(registeredService.getServiceId());
            val audit = AuditableContext.builder()
                .service(service)
                .registeredService(registeredService)
                .build();
            val accessResult = getConfigurationContext().getRegisteredServiceAccessStrategyEnforcer().execute(audit);
            if (accessResult.isExecutionFailure()) {
                return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);
            }
        }
        val token = context.getRequestParameter(OAuth20Constants.TOKEN)
            .map(String::valueOf).orElse(StringUtils.EMPTY);

        return generateRevocationResponse(token, clientId, response);

View on GitHub (pinned to e7288fc434)