Forced Authentication with Apereo CAS


Collaborate
This blog is managed and hosted on GitHub. If you wish to update the contents of this post or if you have found an inaccuracy and wish to make corrections, we recommend that you please submit a pull request to this repository.

This post summarizes a recent conversation I had with a few colleagues on strategies one may use to support forced authentication with CAS and the journey we went on to discover and diagnose a few integration issues with an application protected by mod_auth_cas.

Let’s begin.

The Problem

Some CAS deployments present with the rather common requirement to challenge the user to re-enter credentials in some applications. To accommodate this, it’s likely that deployments may opt into creative yet non-standard solutions such as applications themselves prompting for credentials to replay them in a back-end call. This is problematic for a number of reasons:

  • Applications present a differently styled login form.
  • Applications directly get involved in handling user credentials.

Last but most important:

The only thing necessary for the triumph of evil is for good men to replay credentials.

So, is there a way to force re-authentication with CAS?

Forced Authentication

The CAS protocol has a specific parameter dedicated to forced authentication, named as renew=true.

If supplied as part of an authentication request:

Single sign-on will be bypassed. In this case, CAS will require the client to present credentials regardless of the existence of a single sign-on session with CAS.

If supplied as part of a validation request:

Ticket validation will only succeed if the service ticket was issued from the presentation of the user’s primary credentials. It will fail if the ticket was issued from a single sign-on session.

Version Caveat
If you have deployed CAS 5.2.x, you need to at least be on 5.2.3 for the renew parameter to function correctly.

It goes without saying that renew=true works best if you wish to let the application make that decision when needed. Alternatively, you may also control this behavior centrally by marking the relevant application/service in the CAS service registry (JSON file, etc) such that it would not participate in SSO and would always be asked for credentials regardless of what the application says.

Real Life Example

Let’s say we have an application at https://secure-dev.example.org/groups/ that has SSO enabled. We need one subfolder of that app at https://secure-dev.example.org/groups/reauth/ to require re-authentication to access.

So, (assuming a JSON service registry), we have one service registration record requiring SSO to cover any app on the host. Note that SSO participation is by default on and it’s perfectly good for us to practice laziness here.

{
  @class: org.apereo.cas.services.RegexRegisteredService
  serviceId: ^https?://[\w]+-dev.example.org/.*
  name: secure-dev.example.org
  id: 1200
  description: Everything
  evaluationOrder: 1200
}

We set up another service registration with a lower (i.e. processed first; think of it like the Olympics rankings) evaluation order and ssoEnabled: false to cover the subfolder:

{
  @class: org.apereo.cas.services.RegexRegisteredService
  serviceId: ^https?://[\w]+-dev.example.org/groups/reauth/.*
  name: secure-dev.example.org Groups Reauth
  id: 1200
  description: Groups Reauth
  evaluationOrder: 1100
  accessStrategy: {
    @class: org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy
    ssoEnabled: false
  }
}

Our application, protected by mod-auth-cas has the following configuration:

<Location /groups/reauth>
    AuthType CAS
    AuthName "CAS"
    CASScope /groups/reauth
    CASAuthNHeader SOME_LoginID
    CASScrubRequestHeaders On
    CASCookie MOD_AUTH_CAS_REAUTH
    Options None
    require valid-user
    Order allow, deny
    Allow from all
</Location>

<Location /groups>
    AuthType CAS
    AuthName "CAS"
    CASScope /
    CASAuthNHeader SOME_LoginID
    CASScrubRequestHeaders On
    CASCookie MOD_AUTH_CAS
    Options None
    require valid-user
    Order allow, deny
    Allow from all
</Location>

At first glance, this should do exactly as you would expect if only it weren’t for a small caveat.

The Caveat

If the request begins with https://secure-dev.example.org/groups/reauth/ first and then goes to https://secure-dev.example.org/groups/, the user would be prompted to log in - as expected because those locations use different cookie names. However, once logged in, the user would get caught in a redirect loop between mod_auth_cas and CAS. It turns out that there were two issues. One was that more precise location is placed first in the config:

<Location /groups/reauth>
   CASCookie MOD_AUTH_CAS_REAUTH
   CASScope /groups/reauth/
   ...
<Location>

<Location /groups>
   CASCookie MOD_AUTH_CAS
   CASScope /
   …
<Location>

This made mod_auth_cas use the last applicable CASScope directive which meant that the MOD_AUTH_CAS_REAUTH cookie was being set at Path=/ instead of Path=/groups/reauth/.

This might have been fine except for the way mod_auth_cas parses the values in the Cookie header. It tokenizes the header on ; then iterates through each cookie string by matching the number of characters equal to the length of the expected cookie name defined by the CASCookie directive. If the cookie string starts with the CASCookie name, it skips the next character, assuming that it’s = and takes all remaining characters. So what was happening was that, given the cookie header Cookie: BLARG=WuzzleWuzzle;MOD_AUTH_CAS_REAUTH=foofoofoofoo;, mod_auth_cas was matching the cookie string MOD_AUTH_CAS_REAUTH=foofoofoofoo with the CASCookie name of MOD_AUTH_CAS. Finding that, it skipped the _ and returned REAUTH=foofoofoofoo as the CAS cookie value. That isn’t a valid format for a CAS cookie so it redirected back to CAS. CAS, finding a valid TGC, then performed SSO and redirected back to the service. The service ticket was never validated because mod_auth_cas checks for the presence of a cookie before it checks for the ticket parameter. Finding the invalid cookie again, it redirected back to CAS and so on until the browser threw up its hands in defeat.

So, just something to look out for:

  • When using mod_auth_cas, configuration elements that share a scope (e.g. /groups, /groups/reauth) MUST be listed in order of least to most precise.
  • The CASCookie directives that share a scope MUST NOT be substrings of each other.

Summary

I hope this review was of some help to you and I am sure that both this post as well as the functionality it attempts to explain can be improved in any number of ways. Please feel free to engage and contribute as best as you can.

…and of course, a very special thanks to all colleagues who took part in this post, exchanged dialogue, verified behavior and generously took the time to share their analysis and findings. Thank you.

Misagh Moayyed

Related Posts

CAS 6.0.0 RC3 Feature Release

...in which I present an overview of CAS 6.0.0 RC3 release.

Apereo CAS - Multifactor Authentication with RADIUS

Learn how Apereo CAS may be configured to trigger multifactor authentication using a RADIUS server and its support for the Access-Challenge response type.

CAS Vulnerability Disclosure

Disclosure of a security issue with the MFA features.

CAS 6.0.0 RC2 Feature Release

...in which I present an overview of CAS 6.0.0 RC2 release.

Apereo CAS - dotCMS SAML2 Integration

Learn how to integrate dotCMS, a Content Management System and Headless CMS, with Apereo CAS running as a SAML2 identity provider.

Effective Software Troubleshooting Tactics

A collection of what hopefully are obvious troubleshooting tactics when it comes to diagnosing software deployment issues and configuration problems.

Apereo CAS - MaxMind Geo2IP ISP Integration

Learn how you may determine the Internet Service Provider, organization name, and autonomous system organization and number associated with the user's IP address in CAS using MaxMind services and present warnings in the authentication flow for the end-user if an IP address is matched.

Notes from Better by Design 2018

Be interested in humans and human success.

Apereo CAS - Authentication Lifecycle Phases

Tap into the Apereo CAS authentication engine from outside, and design extensions that prevent an unsuccessful authentication attempt or warn the user after-the-fact based on specific policies of your choosing.

CAS 6.0.0 RC1 Feature Release

...in which I present an overview of CAS 6.0.0 RC1 release.