{"record":{"id":"d446c780e5c0a17f","repo":"apereo/cas","slug":"access-denied","errorCode":"access_denied","errorMessage":"Service [{}] requests authentication","messagePattern":"Service \\[(.+?)\\] requests authentication","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20RevocationEndpointController.java","lineNumber":73,"sourceCode":"    public ModelAndView handleRequest(final HttpServletRequest request,\n                                      final HttpServletResponse response) throws Throwable {\n        val context = new JEEContext(request, response);\n\n        if (!verifyRevocationRequest(context)) {\n            LOGGER.error(\"Revocation request verification failed. Request is missing required parameters\");\n            return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);\n        }\n\n        val manager = new ProfileManager(context, getConfigurationContext().getSessionStore());\n        val callContext = new CallContext(context, getConfigurationContext().getSessionStore());\n        val clientId = getConfigurationContext().getRequestParameterResolver()\n            .resolveClientIdAndClientSecret(callContext).getLeft();\n        val registeredService = getRegisteredServiceByClientId(clientId);\n        RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(registeredService);\n        \n        if (OAuth20Utils.doesServiceNeedAuthentication(Objects.requireNonNull(registeredService))) {\n            if (manager.getProfile().isEmpty()) {\n                LOGGER.warn(\"Service [{}] requests authentication\", clientId);\n                return OAuth20Utils.writeError(response, OAuth20Constants.ACCESS_DENIED);\n            }\n        } else {\n            val service = getConfigurationContext().getWebApplicationServiceServiceFactory()\n                .createService(registeredService.getServiceId());\n            val audit = AuditableContext.builder()\n                .service(service)\n                .registeredService(registeredService)\n                .build();\n            val accessResult = getConfigurationContext().getRegisteredServiceAccessStrategyEnforcer().execute(audit);\n            if (accessResult.isExecutionFailure()) {\n                return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);\n            }\n        }\n        val token = context.getRequestParameter(OAuth20Constants.TOKEN)\n            .map(String::valueOf).orElse(StringUtils.EMPTY);\n\n        return generateRevocationResponse(token, clientId, response);","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20RevocationEndpointController.java#L55-L91","documentation":"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'.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Authenticate the user via CAS (SSO session) before invoking the revocation endpoint so a profile exists","If the revocation is intended to be client-only, adjust the service definition so it does not require authentication","Verify session cookies are forwarded by the client/proxy and the session is not expired","Check OAuth20Utils.doesServiceNeedAuthentication logic and the service definition's access settings to match the intended flow"],"exampleFix":"// before: service definition forces authentication for a headless client\n{\n  \"@class\": \"org.apereo.cas.support.oauth.services.OAuthRegisteredService\",\n  \"clientId\": \"myClient\",\n  \"serviceId\": \"^https://app.example.org/.*\"\n}\n// after: call revocation within an authenticated session, or relax the requirement\n// (login first at /cas/login, then POST /cas/oauth2.0/revoke with session cookies)","handlingStrategy":"validation","validationCode":"// ensure an authenticated profile exists before calling revocation\nasync function canRevoke(profileManager) {\n  const profile = await profileManager.getProfile();\n  return profile != null && profile.isDefined;\n}\nif (!(await canRevoke(profileManager))) {\n  await casLogin(); // establish SSO session first\n}","typeGuard":"function isAuthenticated(profile) {\n  return profile != null && typeof profile.id === 'string' && profile.id.length > 0;\n}","tryCatchPattern":"const res = await post(revokeUrl, form, { withCredentials: true });\nif (res.status === 401 && res.data?.error === 'access_denied') {\n  // no authenticated profile: redirect to CAS login, then retry revocation\n  await casLogin();\n  return post(revokeUrl, form, { withCredentials: true });\n}","preventionTips":["Establish a CAS SSO session before calling revocation for services that require authentication","Forward session cookies through proxies (withCredentials / cookie passthrough)","Re-check service definition authentication requirements for headless clients","Handle expired sessions by re-authenticating instead of failing silently"],"tags":["oauth2","revocation","access-denied","authentication"],"backgroundTag":"authentication-required","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}