I've been tracing the code and have made the following observations:
The AuthNRequst from the SP comes into CAS and a TST is created with the
service reference, then you are redirected to IDP. After authenticating
with IDP, you are redirected back to CAS, which triggers
DelegatedClientAuthenticationAction.doExecute(final
RequestContext context)
<https://github.com/apereo/cas/blob/5.3.x/support/cas-server-support-pac4j-webflow/src/main/java/org/apereo/cas/web/flow/DelegatedClientAuthenticationAction.java#L184>
.
For the first login, *singleSignOnSessionExists(context) [L188]* returns
false. As a result of this, *restoreAuthenticationRequestInContext(context,
webContext, clientName) [L212]* is called which restores the details from
the TST, including the requested service reference.
After logout of SP and new AuthNRequest,
*singleSignOnSessionExists(context) [L188]* returns true because the
previously issued TGT is still in the webflow scope and the TGT is not
expired. Because of this, the code takes a different
path, restoreAuthenticationRequestInContext is not called, instead the
service is set by calling *resolveServiceFromRequestContext(context) [L204]*,
which returns null.
With debugging, I have seen that on the subsequent logins, if I
have singleSignOnSessionExists return false like it does the first time
through, it appears to work as desired and you land back in the client app
every time. That points me to the TGT being in the webflow is causing this
behavior. Thoughts?
DelegatedClientAuthenticationAction.doExecute code for reference:
@Override
public Event doExecute(final RequestContext context) {
final HttpServletRequest request =
WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
final HttpServletResponse response =
WebUtils.getHttpServletResponseFromExternalWebflowContext(context);
if (!isLogoutRequest(request) &&
singleSignOnSessionExists(context)) {
final String tgt = WebUtils.getTicketGrantingTicketId(context);
final Optional<Authentication> authnResult =
getSingleSignOnAuthenticationFrom(context);
if (authnResult.isPresent()) {
final Authentication authentication = authnResult.get();
final Object clientNames = authentication.getAttributes()
.getOrDefault(ClientCredential.AUTHENTICATION_ATTRIBUTE_CLIENT_NAME, new
ArrayList<>());
final String clientName =
CollectionUtils.firstElement(clientNames).map(Object::toString).orElse(StringUtils.EMPTY);
final Service service =
resolveServiceFromRequestContext(context);
if (isDelegatedClientAuthorizedFor(clientName, service)) {
LOGGER.debug("An existing single sign-on session
already exists. Skipping delegation and routing back to CAS authentication
flow");
prepareForLoginPage(context);
return resumeWebflow();
}
}
final Service resolvedService =
resolveServiceFromRequestContext(context);
LOGGER.debug("Single sign-on session in unauthorized for
service [{}]", resolvedService);
centralAuthenticationService.deleteTicket(tgt);
}
final String clientName =
request.getParameter(Pac4jConstants.DEFAULT_CLIENT_NAME_PARAMETER);
LOGGER.debug("Delegated authentication is handled by client name
[{}]", clientName);
if (hasDelegationRequestFailed(request,
response.getStatus()).isPresent()) {
throw new IllegalArgumentException("Delegated authentication
has failed with client " + clientName);
}
final String logoutEndpoint =
request.getParameter(SAML2ServiceProviderMetadataResolver.LOGOUT_ENDPOINT_PARAMETER);
final J2EContext webContext =
Pac4jUtils.getPac4jJ2EContext(request, response);
if (StringUtils.isNotBlank(clientName)) {
final Service service;
if (StringUtils.isBlank(logoutEndpoint)) {
service = restoreAuthenticationRequestInContext(context,
webContext, clientName);
} else {
service = null;
}
final BaseClient<Credentials, CommonProfile> client =
findDelegatedClientByName(request, clientName, service);
final Credentials credentials;
try {
credentials = client.getCredentials(webContext);
LOGGER.debug("Retrieved credentials from client as [{}]",
credentials);
if (credentials == null) {
throw new IllegalArgumentException("Unable to determine
credentials from the context with client " + client.getName());
}
} catch (final Exception e) {
return handleException(webContext, client, e);
}
final ClientCredential clientCredential = new
ClientCredential(credentials, client.getName());
WebUtils.putCredential(context, clientCredential);
WebUtils.putService(context, service);
final Service resolvedService =
authenticationRequestServiceSelectionStrategies.resolveService(service);
final RegisteredService registeredService =
servicesManager.findServiceBy(resolvedService);
WebUtils.putRegisteredService(context, registeredService);
return super.doExecute(context);
}
prepareForLoginPage(context);
if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) {
return stopWebflow();
}
return error();
}
On Wednesday, November 22, 2023 at 12:11:38 AM UTC-5 Justin Isenhour wrote:
> Hello,
>
> I'm hoping someone may have a suggestion of where I can look for the root
> of this problem.
>
> We are running CAS 5.3.16 and have a mix of authentication handlers setup
> including several LDAP auth handlers, delegated auth to AzureAD via OIDC,
> and SAML delegated auth to various other IDPs. We have a SAML client that
> is sending an AuthNRequest with ForceAuthn="true" that is not working as
> expected when CAS uses Delegated auth.
>
> On the first login request, everything seems to be working fine. If you
> log out of that client application, then login again, you get prompted for
> authentication as expected, but instead of being redirected back to the
> requested client, CAS directs to the the generic success page.
>
> This is only an issue when authentication is done via delegated
> authentication client, saml and oidc but have the same issue. If
> authentication is done directly in CAS via LDAP auth handler, then the flow
> works as expected and you land back into the app every time.
>
> I have CAS source code and am pretty familiar with the code, we been using
> CAS since 3.x, but I haven't been able to pin point the issue yet. Anyone
> have any advice or suggestions?
>
> Thanks in advance,
> Justin Isenhour
>
>
--
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
---
You received this message because you are subscribed to the Google Groups "CAS
Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/a/apereo.org/d/msgid/cas-user/2e30add2-1071-4707-abfd-fd9bde9c6757n%40apereo.org.