OHDSI Home | Forums | Wiki | Github

Tips for configuring CAS 2.0 SSO

It can be quite difficult to figure out the correct configuration for the mere 5 CAS parameters because the documentation lacks a meaningful template. But with the right starting point this may be one of the easiest for enterprise authentication (AD or LDAP has presented multiple challenges for us for both authentication and authorization). Here is a redacted working version of the parameters in the Broadsea webtools docker-compose environment .env file (substitute . for _ if not using Docker, but note for some unknown reason in our experience any pom.xml parameter using dot notation instead of underscores in the Docker environment is ignored).

security_oauth_callback_ui=https://[your_atlas_server]/atlas/#/welcome
security_cas_loginUrl=https://[cas_server]/idp/profile/cas/login
security_cas_callbackUrl=https://[your_atlas_server]/WebAPI/user/cas/callback?client_name=CasClient
security_cas_serverUrl=https://[cas_server]/idp/profile/cas
security_cas_casticket=ticket

  1. If oauth_callback_ui is not updated from the default, you will see the browser land on an invalid localhost page after successfully validating the CAS ticket. The default might work if testing locally, but fails for a remote server connection. It is necessary to change this despite not using oauth. It is merely an intermediate URL, as it will redirect to the home page after authenticating.

  2. Your identity and access management team can provide you with the URL to access the CAS server (in our case, the settings were posted on an internal web page). The base URL is item 4, and ā€œloginā€ is most likely going to be appended to the path for the loginUrl for item 2. During login, a casurl attribute is appended to this URL, the value of which is the callbackUrl + client_name=CasClient. This is not obvious from source, but is clear when debugging.

  3. callbackUrl should be structured exactly like this. It would be useful if pom.xml had this as the default. The client_name=CasClient key/value pair is required. During ticket validation, Atlas will send 2 attributes, ticket and service, where the value of ticket is the ticket received from the CAS server, and service is the callbackUrl. If client_name=CasClient is omitted, then the ticket validation step will fail with a service mismatch error presented in the browser, because CasClient is passed during the initial login step, and the CAS server needs to match the casurl sent during the login step (which hard codes the client_name=CasClient to the query string. Fortunately during the construction of the query string at the login step, it does not append the hard-coded client_name pair again if it already exists in the callbackUrl, otherwise CAS likely would be unusable because the server would not validateā€“unless it ignored duplicate pairs).

  4. See 2

  5. The casticket default value is casticket. Instead, the value really should be ā€œticketā€, because that is the standard attribute name returned from probably any CAS server. Using ā€œcasticketā€ resulted in the following error:

javax.servlet.ServletException: java.lang.IllegalStateException: createToken method implementation returned null. A valid non-null AuthenticationToken must be created in order to execute a login attempt.

The other CAS parameter, security_cas_cassvcs, was left with default of null, as debugging revealed it served no purpose.

The IAM person we worked with suggested trying https://[cas_server]/idp/profile/cas/serviceValidate for the callbackUrl. Obviously, this turned out to be a reference to the atlas server callback. But I wanted to mention that the serviceValidate attribute, which is an essential part of the protocol, is not required to be set because the application will append this to the serverUrl setting when performing ticket validation. To convince yourself, you can add serviceValidate to the serverUrl and see two instances of serviceValidate appear in the URL in the error logs.

Another thing to consider is the user name returned from the server, which may or may not correspond to the name entered when signing onto the CAS server. The default received from the server appears in the upper right corner of Atlas, and in our case it was not a particularly user-recognizable name. We requested that the mail field from AD be returned instead. This name appears in the PG sec_user table, and if you switch fields at some point, multiple instances of that user will appear in PG, with anything but the most recent not being usable in the absence of a migration.

One thing you may find helpful in debugging is setting the following in docker-compose .env file:

logging_level_org_apache_shiro=trace
logging_level_root=trace
logging_level_org_ohdsi=trace

Also, information on this site can be helpful for understanding the basics of the protocol:CAS - CAS Protocol

1 Like

This is great information and would become a great wiki article for setting up CAS and WebAPI. Weā€™re also working on improving the documentation for WebAPI, and although weā€™re starting off on documenting endpoints, itā€™s certainly a welcome addition to improve installation/configuration documentation too.

It will be good to hear that someone may benefit from the effort to get this working.

A couple of very important points I forgot to mention is that in the config-local.js file, the ajax parameter must be changed to false. This is a pre-requisite before you have any opportunity to evaluate the protocol in action, as youā€™ll immediately get a ā€œLogin failedā€ message in the login dialog box. And the CAS endpoint is user/login/cas. You donā€™t need to enter credentials on the Atlas server, so it makes sense to set that parameter to false. It should look like this:

    configLocal.authProviders = [{
         "name": "Single Sign On",
         "url": "user/login/cas",
         "ajax": false,
         "icon": "fa fa-cubes",
         "isUseCredentialsForm": false
    }
  ];

After making edits to this JS file, be sure to flush your browser cache/cookies for the changes to take effect on reload to avoid chasing phantom problems.

If you intend to import AD groups via the UI, you should plan to have the ID returned from the CAS server match what AD or LDAP imports into the login field of ohdsi.sec_user during the group import. Otherwise you will see two distinct identities in sec_user and sec_role for the same person, so the imported user login wonā€™t match the currently authenticated user. AD iimport seems fixed on only importing sAMAccountName, which in our environment is not available from CAS due to uniqueness issues. Your mileage may vary. Using remote debugging, it appears the following setting does not impact the import field: ā€˜security_ad_userMapping_usernameAttr=userPrincipalNameā€™, so Iā€™m not sure what it does since it doesnā€™t seem to change the login name used for authentication either.

However, I see that the most recent update (post-2.11.1) to source Iā€™m speculating will allow you to specify the AD login attribute on import with a new field in the code, security.ad.userImport.loginAttr. This is great for us since we canā€™t get either AD or CAS authentication to return a matching sAMAccountName. Meanwhile, if you are building WebAPI, I believe you could change the following returned value:

  public String getLoginAttributeName() {
    return "sAMAccountName";
  }

to the attribute that will match the returned CAS user field, but if using Docker then youā€™ll need to wait for the next release of Broadsea to contain this update, or install an updated WebAPI.war and atlas.zip into the container as part of the container startup since the Docker images arenā€™t updated very often.

Do you think this should be made into a configuration variable?

What I was trying to say is that, if I understand the code, it looks like it was just addressed as part of 2059 as new fields for login (and also username), and loginAttr is in the latest pom.xml:

  @Value("${security.ad.userImport.loginAttr}")
  private String loginAttr;

ā€¦

  @Override
  public String getLoginAttributeName() {
    return loginAttr;
  }

so itā€™s just a matter of availability at this point. Meanwhile, Iā€™m already working on python code to replicate various user management tasks.

t