/
CAS and Duo

CAS and Duo

 

Duo (duosecurity.com) is a cloud based secondary authentication service. After the user has logged in with a userid and password, it presents a second page where the user can interact with the Duo service using a specific cell phone or tablet tied to his userid to approve and complete the login. Duo provides Java classes that allow CAS to integrate with its service.

Unicon supports Duo as one of the available alternatives in its MultiFactor Authentication (MFA) package.However, the Unicon package introduces additional dependency libraries that conflict with JBoss, and it supports other forms of secondary authentication that Yale is not currently interested in using. More importantly, we do not yet understand the rules we will use for Duo, and so we need a small clean integration that just does Duo in the simplest possible way. Because Duo supports integration with any Web application, it is fairly easy to simply treat CAS like any other Java Web app and to treat Duo as any other WebFlow state.

The Duo Setup

In the Duo Administrative panels, one creates an "Integration" (see Creating Integrations). Integrations have types. "CAS" is one of the types of integrations, but there is no reason to believe that Duo treats CAS any different from a generic Web application. Duo provides some JavaScript that uses an HTML iframe to communicate to the Duo Web servers and manage the second authentication. Although Duo provides helper libraries for Java (and every other common programming language), the Duo Servers communicate to the code running on the Browser and not to the code running on our Web Servers. Therefore, Duo doesn't really know or care that the application is CAS or even that it is written in Java.

Each Integration you define is assigned three values by Duo and there is a fourth string you supply yourself. They are:

  • The Integration Key which is an application identifier to Duo.
  • The Secret Key which you share with Duo and treat as a password so you don't let unauthorized users see it.
  • The hostname of the Duo server to which the iframe is connected when running the application.
  • An Application Key that you generate (use any automatic password generator) and then keep secret from everyone including Duo.

You can define more than one Integration/Application on the same account. All Integrations share the same set of users. Because all iframe applications behave the same, the only reason for a different Integration is when you want to communicate from your application directly to Duo using a different API.

The Yale version of CAS Duo support has a second optional module that uses what Duo calls its "Auth API". This is a restful Web Service that can be used from any application that does not want to display the iframe and does not want its users to communicate directly to Duo. For example, a company that was able to register all its users and devices to Duo in advance would have no reason to ask the user through the iframe if he wants to use the smartphone App or get a text message. Everyone uses the App. In such a company, after the user presents his userid and password then one could program CAS to use the "Auth API" to unconditionally trigger "Duo Push" to the smartphone App for everyone, and there is no need for a Duo page or an iframe dialog.

However, neither Yale nor any other university is that carefully controlled. The Yale CAS code uses the Auth API just to perform a prelimnary "/preauth" Web Service check on the Netid. Duo returns an indication if that Netid has been registered with Duo as a user. If so, CAS switches back to the standard interactive iframe Integration and presents the standard Duo user interface. If the Netid is not registered then there is nothing that Duo can do for this user, so CAS bypasses Duo processing for this login.

This works because although Duo defines two Integrations for the two different API's, there is only one pool of usernames shared by the two Integrations. So every Netid has the same characteristics to the Auth API and the iframe dialog.

The four parameters (Integration Key, Secret Key, Application Key, and Hostname) have to be configured to CAS. If you use both the iframe API and the Auth API /preauth, there are two sets of parameters and both have to be configured (although it makes sense to use the same Application Key since we choose it, and the Hostname appears to be the same for all Integrations on the same account). If you know anything about CAS, parameters like this are configured in XML that defines Spring Beans. At Yale, parameters like this are passed to all Yale applications during the Jenkins Install job processing. Secret parameters like the Secret Key and Application Key are passed by the sysadmin that starts the Jenkins job, while nonsensitive parameters (the Application Key and Hostname) are configured in a *.properties file checked into Subversion.

It is also possible to use the Duo Admin pages to configure profiles for the Users, profiles for the Integrations, and Groups of Users. It is not clear that any of these options are useful for Yale CAS itself. However, CAS is really an authentication front end for other applications, and they may have various security requirements. As Yale becomes more sophisticated in its use of Duo, we may find it convenient to define additional Duo Integrations with different profiles, and to program CAS to select an Integration based on information about the application (the CAS Service) based on the Service Registry. One you have two separate Integration Beans with their own parameters, it is a trivial programming exercise to generalize this to three or more.

CAS Beans

CAS uses Spring Beans configured to do a particular job, and Spring WebFlow to decide which beans to call and which tests to make.

Duo is part of the user interface because it interacts with Web pages, HTML, HTTP, and so on. Spring Beans that are part of the user interface layer are traditionally defined in the WEB-INF/cas-servlet.xml file. Spring Beans that are part of the backend services (business logic, data access, ...) are traditionally defined in other files. Spring doesn't really care, so this is mostly a convention. We follow it and define the Beans that interact with Duo in cas-servlet.xml.

    <bean id="duoService"
          class="edu.yale.cas.duo.DuoAuthenticationService"
          p:duoIntegrationKey="${cas.duo.integration.key}"
          p:duoSecretKey="${cas.duo.secret.key}"
          p:duoApplicationKey="${cas.duo.application.key}"
          p:duoApiHost="${cas.duo.api.host}" />

This XML creates a Bean named "duoService" (at least it has that name to other Beans and to Spring WebFlow). It is an instance of the edu.yale.cas.duo.DuoAuthenticationService Java class. You will find the source to this class in the cas-server-war project checked into Subversion. In CAS terms, this project is our "WAR Overlay" project that builds the Web application installed in JBoss, and it contains all the Yale special configuration and the Yale Java source.

The four parameters to this Bean are the four Integration parameters described above.

If you use the optional Auth API service to /preauth the Netid, then there is a second Bean to define an instance of the edu.yale.cas.duo.DuoVerify class. It also has the same four Duo Integration parameters, but the values for these parameters come from the Auth API Integration instead of the "CAS" (interactive iframe) Integration.

(An aside to Yale developers - in most other Yale applications the values for these "${variable.name}" expressions is inserted into the file to replace the variable reference when Maven or Ant copies the text file to the JBoss deploy directory, but if you looked at the cas.war files you would see that these expressions have not been changed by deployment. Instead, CAS uses a feature of Spring where Spring variables can be configured in a properties file (the cas.properties file generated by the Jenkins Install job and placed next to cas.war in the JBoss deploy directory). Then at runtime Spring substitutes values from cas.properties whenever it encounters a ${variable.name} expression in one of its configuration files.)

In addition to serving as a holder for the four Integration parameter values, the DuoAuthenticationService class exposes two methods. Every Duo application written in any programming language on any Web Server has a version of the same two methods. One function is called before the Duo iframe is displayed, and another validates the data returned by the iframe. After all, JavaScript runs on the Browser and the user could use a Browser side debugger to alter the data, so CAS (or any other protected application) needs to verify that data supposedly forwarded from the Duo Web Server was not tampered with.

However, the exact behavior of the two methods is going to depend on the the programming language, Web Server, and application framework. One Duo supplied generic Java function has to be adapted to support Java Server Faces, or Struts, or (in our case) Spring WebFlow. For example, WebFlow has a convention that methods should return character string results and normally the first two possible return values should be "success" or "error". Other Java frameworks may expect true or false, 0 or 1, or they may require that "error" be represented by an Exception. So for each possible framework you have to write a tiny bit of Java translation code between the routines that Duo provides and the framework you are using. Our DuoAuthenticationService class adapts the generic Duo routines to the "success" or "error" convention of WebFlow.

The generateSignedRequestToken accepts the Netid as a parameter, adds three of the four Integration parameters configured in the Bean XML, and calls the generic function of the Duo supplied Java library to generate the data written to the HTML page that displays the Duo iframe.

Our validateResponse method takes the encoded signed response generated by the browser side Duo-supplied JavaScript and passes it to the Duo supplied DuoWeb.verifyResponse method. Assuming that the user did not corrupt the string using a Browser side JavaScript debugger, the response should validate and the Duo method should return the Netid which can be compared with the original value of the Netid to confirm authentication. Our code then returns "success" if the user verified his login or "error" if he did not.

To Duo or Not to Duo

During the phase in period, some users will be enabled for Duo but most will not. Duo generates a very short lived error message if you send it a person who is not configured, and for someone who is not expecting to see this additional screen on an application as sensitive as CAS, any unexpected behavior should be avoided. Also Duo cannot be expected to be available 100% of the time, so we need a well defined behavior when it is down. These requirements create the need for two optional support Beans.

The DuoVerify Bean calls a secondary Duo programming interface called the AuthAPI. We only use the pre-Authenticate feature of this API.

    <bean id="duoApiService"
          class="edu.yale.cas.duo.DuoVerify"
          p:duoIntegrationKey="${cas.duoapi.integration.key}"
          p:duoSecretKey="${cas.duoapi.secret.key}"
          p:duoApplicationKey="${cas.duo.application.key}"
          p:duoApiHost="${cas.duoapi.api.host}" 
          />

This Bean provides the duoService bean with a method that communicates with the Duo service over the network and makes three checks:

  1. Is Duo up? If we cannot contact Duo then there may be a temporary service interruption that has to be handled according to Yale policy.
  2. Is this user defined to Duo? If not, then the user is not part of the Duo test during the initial rollout period and Duo processing should be bypassed for this login.
  3. Is the client browser in the list of Duo trusted networks? Certain IP addresses are whitelisted and can bypass Duo processing. Machines in these networks do not need a second factor check.

If this method returns true then the duoService proceeds to display the Duo interactive iframe. If it returns false, then Duo is bypassed and the user logs in with just the password.

The Duo AuthAPI is a separate Integration (a separate application in Duo terms) that shares the same set of users. So a user defined to the AuthAPI is also a user from the point of view of the normal Web API. If Duo is up during this preliminary check but fails during the iframe processing, there is nothing we can do to trap that error. The user has to exit and start over again.

The duoService exposes the test to the WebFlow that determines whether the Duo page is displayed. So if you decide to use this Bean, you have to configure the previous duoService bean to use it by adding an additional parameter to the duoService Bean definition:

          p:apiclient-ref="duoApiService"

 When this parameter is provided, it inserts a point to the duoApiService Bean into the duoService bean and then duoService calls it to determine if the Duo iframe page should be displayed for this Netid.

Alternately, you can check for membership in an AD Group:

    <bean id="duoLdapCheck" class="edu.yale.cas.duo.DuoLdapCheck">
            <property name="filter" value="${ad.server.user.filter}" />
            <property name="searchBase" value="${ad.server.user.search.base}" />
            <property name="contextSource" ref="ldapContextSource" />
    </bean>

Yale (and everyone else) uses Active Directory to validate passwords. So the parameters to this Bean are the same as and would already be familiar to anyone using the standard  cas-server-support-ldap Apereo module to validate the password using LDAP protocol. By using the same "ldapContextSource", this Bean shares the same pool of LDAP connections with Active Directory used by the password validation function.

Again, the duoService Bean is called by WebFlow and will use this Bean only if it gets a reference to it. So once you create this Bean, you have to add an extra parameter to the duoService Bean definition:

          p:duoLdapCheck-ref="duoLdapCheck"

The Duo Auth API bean requires no additional configuration. The user is either defined to Duo or he is not. The LDAP lookup is potentially more complicated. There may be a single group, or a set of Groups, or Groups of Groups, or the user may have a special property defined in his User object. At this point Yale is not prepared to create a general purpose LDAP module that can handle all these cases and can be configured in the Bean XML. You probably have to write some Java code and use the Yale code as a starting point.

CAS Login WebFlow

WebFlow is defined as a sequence of named states, each defined by an XML element. Most are "action-state" or "decision-state" elements that perform a test or call a method on some Spring Bean and then jump to a new state depending on the result. A few are "view-state" elements that display a JSP page or redirect the browser. You move from one state to another by configuring transition elements where a returned value like "success" or "error" is used to trigger a jump to a new state by a reference to its XML id.

In the vanilla Apereo CAS login, there are some preliminary decision or action states that test for "renew" or "gateway" and determine if the user has previously signed on and has a CASTGC cookie. Then when appropriate there is one view-state to display the userid/password form. That is then followed by some states to write the Cookie to the browser and to generate a Service Ticket if a service was presented to the login request. There is a final view-state to redirect the Browser back to the Service URL.

Yale already had its own changes to the WebFlow. We have our own code to test for a password that is too old and needs to be changed (because we have our own Netid password management system separate for Active Directory because it dates back to the days when passwords were authenticated to MIT Kerberos). If the password has to be changed, then CAS needs a new view-state to redirect the Browser to our Netid Self Service application that changes passwords. We also have some extra post processing that is done in the action state that checks the values submitted by the netid/password form. So Duo is not our first CAS customization in this area.

Duo presents a second page with an iframe and some JavaScript. This requires a new view-state associated with a second JSP page. There are several ways this page might be integrated into the Web Flow, and the best answer depends on how you view the service that Duo is providing.

Now specific details about placement. In the standard CAS WebFlow, the page with the form that collects the userid and password is associated with a view-state named "viewLoginForm". When the user submits the form, the userid and password are passed to a method of a bean that validates the password and generates a login ticket. Then "success" transitions to the state named "logAuthSuccess" which, not surprisingly, writes into the log file that a user was successfully logged in. That in turn transitions to an action-state named "sendTicketGrantingTicket" that generates the CASTGC login Cookie.

Since Duo extends the userid/password login with a second authentication, the obvious place to insert the Duo iframe page is after the "viewLoginForm" (because Duo needs the Netid) and before the "logAuthSuccess" (because the login isn't successful until Duo processing completes).

So the XML that defines the new Duo WebFlow states is inserted between "viewLoginForm" and "logAuthSuccess". The transition from "sucess" in the processing of the login form now goes to the new Duo states, and the transition from "success" after the processing of the Duo form then transitions to "logAuthSuccess".

There is one detail that needs to be clear to anyone who works with the code. Before the Duo processing was entered, the regular userid/password form was displayed and the password was validated to Active Directory. It is part of CAS system design that when a password is successfully validated, a Logon Ticket (a "TGT") is generated and the TGTID is passed back to the WebFlow to be written as a Cookie (the CASTGC Cookie) to the Browser. The standard Apereo code holds the TGTID in very temporary variable storage ("request" related storage, flushed every time a new Web page is displayed) between the time it is generated and the "sendTicketGrantingTicket" state where it is used to generate the CASTGC Cookie.

Because Duo displays a new Web page, the TGTID has to be copied to a longer lasting variable. Yale copies it to a "flow state" variable which lasts for the entire login process. However, since Apereo standard code is going to look for it in a request state variable, at the end of Duo processing we copy it back from longer term flow state to the short lived request state variable so the "sendTicketGrantingTicket" code finds it where it expects it to be.

If Duo never validates the user, then the Cookie is never created and the user never logs into CAS. However, the TGT remains in CAS memory for two hours until it times out. If you don't like this, feel free to add some code somewhere to use the TGTID to delete the ticket. The small amount of space used doesn't seem worth the effort.

However, this entire management of the TGTID  is only one potential solution based on one way to think about Multi-Factor Authentication. There are really three strategies and which you choose depends on how you view this type of authentication:

  • In the current Yale design and the CAS 3 code, Duo is regarded as a second step for selected users providing additional security to a traditional userid/login authentication. This ties Duo to the user and not to the application. Netid "gilbert" has decided to require Duo for all his logins so that his password cannot be cracked by hackers from the DPRK. However, from a CAS point of view it is the same as a traditional userid/password login with the same type of Login TGT.
  • Another use case might regard userid/password+Duo as a "better", higher grade, more secure Level Of Authentication (LOA in the jargon of this subject) than just a plain userid/password authentication. Of course, Duo does not itself tell us whether the user was registered and provided a second token or was unregistered but "bypass" was enabled (unless you configure the Integration to require Duo registration). If you clean up the configuration and force Duo to provide a strong second token, then you can indicate that this is a better type of Login by deleting the TGT generated by the original userd/password validate and create a new stronger type of TGT containing some indication of its improved strength. You can hack all this into CAS 3, but it is easier to upgrade to CAS 4.
  • Once you get to CAS 4, then it is possible to add a second Authentication object to an existing TGT. So you do not need to discard the first TGT, but rather you can add the Duo Authentication object to the TGT created by userid/password processing. The TGT becomes implicitly stronger when it is bound to the second Authentication.

These three different ways to manage the TGT reflect different institutional objectives for Multi-Factor authentication. Yale's requirements will certainly change over time, but it is not clear that they will change the same way for every user and every application. It is a mistake to try and push the code design far out ahead of the institution's carefully defined requirements. This design is good enough for us now, and the Beans are simple enough and the WebFlow integration is flexible enough to adapt the design to new requirements when they have been fully articulated.

Now it is time to show the actual XML that configures Duo in the login-webflow.xml file. One line is inserted at the beginning of the file because it seems to be a convention that XML that defines new WebFlow variables comes first. The rest of the code (after the "...") is inserted between the old viewLoginForm state and the old logAuthSuccess state:

    <var name="duoStatus" class="edu.yale.cas.duo.DuoStatus" />
	...

	<action-state id="testDuoEnabled" >
        <evaluate expression="duoService.checkDuoEnabled(credentials.username)" />
        <transition on="success" to="viewLoginFormDuo" />
        <transition on="error" to="logAuthSuccess" />
    </action-state>
    
    <!-- 
        Display the Duo Two Factor page.
        We populate the sigRequest field with the generated data, and the apiHost with a configured string.
        The response comes back from the signedDuoResponse field and is stored in the duoStatus object.  
    -->
    <view-state id="viewLoginFormDuo" view="casDuoLoginView" model="duoStatus">
        <binder>
            <binding property="signedDuoResponse"/>
        </binder>
        <on-entry>
            <set name="viewScope.sigRequest" value="duoService.generateSignedRequestToken(credentials.username)" />
            <set name="viewScope.apiHost" value="duoService.getDuoApiHost()" />
            <set name="viewScope.commandName" value="'duoStatus'" />
        </on-entry>
        <transition on="submit" bind="true" validate="false" to="realSubmitDuo"/>
    </view-state>
    <!-- signedDuoReponse should resolve to the Netid -->
    <action-state id="realSubmitDuo">
        <evaluate expression="duoService.validateResponse(duoStatus.signedDuoResponse,credentials.username)" />
        <transition on="success" to="logAuthSuccess" />
        <transition on="error" to="logAuthError" />
    </action-state>

The first action-state (testDuoEnabled) calls a method in duoService passing the Netid. A return value of "success" displays the Duo iframe page while "error" bypasses Duo processing. Exactly when each choice will be made is currently being defined at Yale. Three potential criteria apply:

  1. There is a global "kill switch" in the duoService Bean that turns off Duo authentication for everyone. It is there for undefined emergencies.
  2. The DuoLdapCheck class supports a Spring Bean that tests to see if a Netid is in some Active Directory group.
  3. The DuoVerify class supports a call to Duo's Auth API service to see if the Netid has been added to the set of usernames in the Duo account for Yale.

Typically Netids are added to the Duo account when they are added to an AD Group and then later the Group is synched to Duo during some overnight processing. So 99% of the time the AD check and the Auth API check are two ways to look at the same list of users and they will return the same result. However, it is possible to use the Duo admin console to define users manually, and the Auth API is simpler and Duo does most of the work for us. The AD LDAP check might be a bit faster because the server is located at Yale, but if you care about such things you are old fashioned and do not fully appreciate the wonderfulness of "the Cloud".

Either test (LDAP or Auth API) prevents a user who is not defined to Duo from experiencing a fraction of a second flash of a message in the Duo iframe Window when Duo discovers that it does not know the Netid and has been configured to allow such users to login. However, if you have configured the CAS Integration to work that way you do not know which users are actually getting a second factor of authentication. If you want to support both single and multi-factor authentication in the same CAS, but know who actually gets real multi-factor authentication, then you need the LDAP or Auth API precheck and then configure the CAS Integration to require Duo authentication for anyone who passes the precheck and is presented with the iframe page.

Now back to the WebFlow XML. The second view-state ("viewLoginFormDuo") displays a JSP page (casDuoLoginView.jsp) that contains the standard Duo user interface stuff. The page is pretty much Duo supplied boilerplate to include some JavaScript logic and create an iframe that will hold the Duo UI dialog. Before the page is written, it calls the generateSignedRequestToken method of the duoService Bean described above to generate a complex encrypted signed String based on the Netid and key parameters. This string is called "sigRequest". If you read the source of the casDuoLoginView.jsp page you will see that the page contains a reference to variables defined here:

<script>
    Duo.init({
        'host': '${apiHost}',
        'sig_request': '${sigRequest}',
        'post_argument': 'signedDuoResponse'
    });
</script>

Spring scans the JSP page before it is written to the browser, and when it sees a ${name} expression that references a variable that was set by a <set name="viewScope.sigRequest" ... /> element, it replaces the ${name} with the value. So if you login to CAS, and while you are are looking at the Duo login screen you right click and ask the Browser to View Source you will see that this code in the JSP page has been replaced before it was sent to the Browser with something like:

<script>
    Duo.init({
        'host': 'api-08659818.duosecurity.com',
        'sig_request': 'TX|Z2...abae3',
        'post_argument': 'signedDuoResponse'
    });
</script>

The third argument of this JavaScript call to the Duo supplied init method is the name to assign to the Form field generated by JavaScript to hold the response from Duo when the page returns to Spring WebFlow. Because of the 'post_argument': 'signedDuoResponse' in the page, the Duo JavaScript creates the equivalent of a <input type="hidden" name="signedDuoResponse" /> element in the Form and stores its response data in that form field.

The WebFlow has to be told what field name in the Form will get the Duo response data. That is accomplished by the <binding property="signedDuoResponse"/> element. Finally, the WebFlow needs a place to store the data. The object name is supplied by the model parameter of the view-state (<view-state id="viewLoginFormDuo" view="casDuoLoginView" model="duoStatus">) which is a reference back to the first line of the example code where at the start of the file a variable named duoState was created from an object of the edu.yale.cas.duo.DuoStatus class. Implicitly, this object must contain a property whose name is the same as the name of the Form field (and the binding property, and the third argument of the init function in the JSP page). So if you check the source of edu.yale.cas.duo.DuoStatus, you will find that the only thing in this class is a property named signedDuoResponse and its only purpose is to receive the data generated by the Duo login.

There is then a trivial transition to the action-state named "realSubmitDuo" that handles the value stored in duoStatus.signedDuoResponse. Specifically, it passes this string to the validateResponse method of our duoService bean where the string, and three of the four Integration parameters, are passed to the Duo supplied Java code that decodes the string, validates the signature, and returns the Netid of the Duo validated user. Assuming that the user completed authentication, a return value of "success" now completes the processing of our inserted code and picks up where the vanilla Apereo code left off by transitioning to the "logAuthSuccess" state.

It is a design point to be resolved later whether the JSP page should provide an exit point if the user cannot complete Duo processing, and exactly where that Cancel should go and what it should display is not currently defined. However, everyone will at some time enter their userid and password, and then when Duo page is displayed they will reach for their cell phone and realize it is still in the charger at home on the night stand next to their bed. Exactly what option we will provide or what advice we will offer has not yet been determined.

The boilerplate on the rest of the casDuoLoginView.jsp page is not interesting. It is basically a Spring+JSP version of the standard HTML that every Duo application written in any server programming language generates to trigger the Duo user interface through the Duo supplied JavaScript.

However, those unfamiliar with Spring or CAS may be surprised at the location of the page. The user comes to CAS at the "https://secure.its.yale.edu/cas/login" URL, so from the HTTP point of view anything that comes back appears to be in the root directory of the /cas application on the server. If this was a real JSP page, that would be where you have to put it. However, this JSP page is modified by Spring to insert values for the ${name} expressions that appear in the page before the data is written to the browser. So by Spring conventions, the page has to be put in the unexpectedly complicated WEB-INF/view/jsp/default/ui directory (along with the casLoginView page and all the other CAS UI pages. Also note that this, like every new JSP page added to CAS login, has to be added by name to the WEB-INF/classes/default_views.properties file.

Architecture

There were extensive discussions when CAS 3 was developed about the system architecture. The code and configuration mentioned above conforms to the basic CAS design principles. No Apereo code was modified. New code was created within the WAR Overlay project (which is the recommended way to develop local customizations) and was inserted into the WebFlow with standard configuration elements. Everything should port to the next release of CAS without requiring any changes.

Duo supplies the Java source of four classes in the com.duosecurity.duoweb package. Because they are small and do not appear to be available as a Maven dependency JAR, the source has been added to the src/main/java directory of the WAR Overlay project and they are compiled along with Yale source. These are the only classes we use, so unlike the Unicon packages, there are no additional dependency JARs added to the project and no problems running the code in JBoss.

Is Duo a Credential?

For now, the answer to this question is "No".

CAS has the ability to validate different Credential objects. If the user enters a Netid and Password in the CAS login form, they are combined into a Credential object that is validated by one of the configured CAS Authentication Handlers. Handlers get to look at the Credential and validate it or pass it on, so "gilbert", "howard.gilbert@yale.edu", and "gilbert@NET.YALE.EDU" as different values typed into the Netid field could in theory have their password validated against different LDAP, Kerberos, database, or Web Service back end systems.

In CAS 4 there is at least the beginning of a concept that some Credentials are "better" than others. You could claim that Duo authentication is better than password authentication. To do that, you have to make Duo authentication a different type of Credential than simple userid/password and you have to give it its own Authentication Handler.

This is not part of the current Duo requirement at Yale, so Duo is not treated as a new type of Credential. Rather, it is an additional user interaction built into the Web Flow that validate the ordinary Userid/Password. Nothing in CAS outside the Web Flow knows whether Duo authentication occurs or not.

This could change with new requirements, but then it would change the way you use CAS, and require a new Authentication Handler, and it probably is not worth considering until we migrate to CAS 4.

Is CAS A Single Duo "Integration"

The current code (and the original Unicon code it is based on) treats CAS as a single Duo "Integration". That is, there is a single duoService bean with a single copy of the four Integration parameters. This means, however, that all CAS logins share a single Duo policy.

However, CAS is a shared authentication mechanism for thousands of services. If Yale implements the Service Registry feature of CAS, it could behave differently for different values of the service= URL string.

It is likely that some services will want to make Duo authentication option, while others will want to make it required. The easiest way to do that is to define two Duo Integrations with different policies and different sets of the four parameters. Then a specific user login will be assigned the Integration parameters that correspond to the policy requirements of the service to which he is logging in.

By stripping the code down to the minimum required, and by using straight forward configuration described here in detail, we are now prepared to respond to future requirements by making more complex use of Duo configurations. There will be some extra code to write, but it will be simple and it is obvious what to do and where to do it.

The Duo application on the user's phone accepts multiple accounts on multiple integrations or systems, so this is transparent to the user.