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.

Reply via email to