apereo/cas · warning
Unable to detect authenticated user profile for prompt-less…
Error message
Unable to detect authenticated user profile for prompt-less login attempts. Redirecting to URL [{}] What it means
When an OIDC authorization request arrives without an explicit prompt value and CAS cannot detect an already-authenticated user profile in the request context, it cannot silently authorize the request. CAS logs this warning and redirects the user to the login flow, appending an error/status (e.g. login_required for non-form-post response modes) to the original redirect URL.
Solutions
- Have the client send an explicit prompt parameter (e.g. prompt=login) so CAS takes a deterministic path.
- Ensure CAS SSO sessions are valid: verify ticket registry replication across nodes and session timeout settings.
- Check browser cookie settings/third-party cookie blocking if the flow runs in an iframe.
- Confirm the user logged in to CAS before the OIDC request; the redirect is usually correct behavior, not a defect.
- If redirect loops occur, inspect the built redirect URL and response mode to confirm the client handles the login_required error.
Example fix
// before GET /cas/oidc/authorize?client_id=app&response_type=code&redirect_uri=... // after GET /cas/oidc/authorize?client_id=app&response_type=code&prompt=login&redirect_uri=...
Defensive patterns
Strategy: fallback
Validate before calling
// Client-side: check for an existing CAS session cookie before a prompt-less authorize call;
boolean hasCasSsoSession(jakarta.servlet.http.HttpServletRequest req) {
return req.getCookies() != null && java.util.Arrays.stream(req.getCookies())
.anyMatch(c -> c.getName().startsWith("TGC"));
} Prevention
- Send an explicit prompt parameter (login/consent) when silent SSO is not a requirement.
- Ensure the ticket registry is shared/replicated across all CAS nodes behind the load balancer.
- Avoid embedding the authorize flow in third-party iframes where cookies may be blocked.
- Handle the login_required / login redirect in the OIDC client instead of assuming silent success.
When it happens
Trigger: OidcCallbackAuthorizeViewResolver.resolve processes an authorize/callback URL where prompt is absent or lacks login/none, and the authenticated profile lookup finds nothing (expired SSO session, missing ticket cookie), so CAS falls back to authorizationModelAndViewBuilder.build with a login redirect.
Common situations: User's CAS SSO session expired before the prompt-less authorization request; cookies blocked in an iframe so no session profile is detected; CAS nodes behind a load balancer without shared ticket registry; client relies on silent SSO while no session exists.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Denied
- Invalid cookie . Required user-agent does not match
- JWKS cannot contain expressions
- Unable to use 'none' as introspection signing algorithm
- Unable to use 'none' as introspection encryption algorithm
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/34c86783561dfda0.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-core-api/src/main/java/org/apereo/cas/oidc/web/OidcCallbackAuthorizeViewResolver.java:67
val model = new HashMap<String, String>();
model.put(OAuth20Constants.ERROR, OidcConstants.LOGIN_REQUIRED);
return new ModelAndView(new JacksonJsonView(), model);
}
val parameters = new LinkedHashMap<String, String>();
parameters.put(OAuth20Constants.ERROR, OidcConstants.LOGIN_REQUIRED);
oauthRequestParameterResolver.resolveRequestParameter(context, OAuth20Constants.STATE)
.ifPresent(state -> parameters.put(OAuth20Constants.STATE, state));
val clientId = oauthRequestParameterResolver.resolveRequestParameter(context, OAuth20Constants.CLIENT_ID).orElse(StringUtils.EMPTY);
val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(servicesManager, clientId);
OAuth20Utils.validateRedirectUri(originalRedirectUrl.get(), true);
val responseType = oauthRequestParameterResolver.resolveResponseModeType(context);
val redirect = FunctionUtils.doIf(OAuth20ResponseModeFactory.isResponseModeTypeFormPost(registeredService, responseType),
originalRedirectUrl::get,
() -> OidcRequestSupport.getRedirectUrlWithError(originalRedirectUrl.get(), OidcConstants.LOGIN_REQUIRED, context))
.get();
return FunctionUtils.doUnchecked(() -> {
LOGGER.warn("Unable to detect authenticated user profile for prompt-less login attempts. Redirecting to URL [{}]", redirect);
return authorizationModelAndViewBuilder.build(registeredService, responseType, redirect, parameters);
});
}
if (prompt.contains(OidcConstants.PROMPT_LOGIN)) {
LOGGER.trace("Removing login prompt from URL [{}]", url);
val newUrl = OidcRequestSupport.removeOidcPromptFromAuthorizationRequest(url, OidcConstants.PROMPT_LOGIN);
LOGGER.trace("Redirecting to URL [{}]", newUrl);
return OAuth20CallbackAuthorizeViewResolver.asDefault().resolve(context, manager, newUrl);
}
LOGGER.trace("Redirecting to URL [{}]", url);
return OAuth20CallbackAuthorizeViewResolver.asDefault().resolve(context, manager, url);
}
}
View on GitHub (pinned to e7288fc434)