Apereo CAS - Delegated Authentication with Amazon Cognito

Posted by Misagh Moayyed on December 15, 2020 · 7 mins read ·

Apereo CAS has had support to delegate authentication to external OpenID Connect identity providers for quite some time. This functionality, if memory serves me correctly, started around CAS 3.x as an extension based on the pac4j project which then later found its way into the CAS codebase as a first-class feature. Since then, the functionality more or less has evolved to allow the adopter less configuration overhead and fancier ways to automated workflows.

Of course, delegation is just a fancy word that ultimately means, whether automatically or at the click of a button, the browser is expected to redirect the user to the appropriate identity provider endpoint, and on the return trip back, CAS is tasked to shake hands, parse the response and extract attributes, etc to establish an authentication session, issue tickets, etc. In other words, in delegated scenarios, the main identity provider is an external system and CAS simply begins to act as a client or proxy in between.

In this blog post, we will start from a modest OpenID Connect client application that is integrated with CAS and will be using Amazon Cognito as our external OpenID Connect identity provider to accommodate the following authentication flow:

  • User accesses OpenID Connect client application.
  • User is redirected to CAS, acting as an OpenID Connect identity provider.
  • CAS, acting as a client itself, lets the user delegate the flow to Amazon Cognito.
  • User logs in using Amazon Cognito credentials and is redirected back to CAS.
  • CAS establishes an SSO session and redirects the user back to the OpenID Connect client application.
  • OpenID Connect client application shakes hands with CAS and allows the user to login.

Our starting position is as follows:

Configuration

Once you have the correct modules in the WAR overlay for OpenID Connect and Delegated Authentication, you will need to make sure CAS is able to hand off authentication to the Amazon Cognito identity provider:

cas.authn.pac4j.oidc[0].generic.id=abcdefgh
cas.authn.pac4j.oidc[0].generic.secret=1234567890
cas.authn.pac4j.oidc[0].generic.discovery-uri=\
  https://cognito.amazonaws.com/xyz/.well-known/openid-configuration
cas.authn.pac4j.oidc[0].generic.client-name=AwsCognitoOidcClient

Of course, we also need to make sure our OpenID Connect client application is registered with CAS:

{
  "@class": "org.apereo.cas.services.OidcRegisteredService",
  "clientId": "client-id",
  "clientSecret": "secret",
  "serviceId": "https://my-redirect-uri",
  "name": "OIDC",
  "id": 1,
  "supportedGrantTypes": [ "java.util.HashSet", [ "authorization_code" ] ],
  "supportedResponseTypes": [ "java.util.HashSet", [ "code" ] ],
  "attributeReleasePolicy": {
    "@class": "org.apereo.cas.services.ChainingAttributeReleasePolicy",
    "policies": [
      "java.util.ArrayList",
      [
        {
          "@class": "org.apereo.cas.oidc.claims.OidcProfileScopeAttributeReleasePolicy",
          "order": 0
        },
        {
          "@class": "org.apereo.cas.services.ReturnMappedAttributeReleasePolicy",
          "order": 1,
          "allowedAttributes" : {
            "@class" : "java.util.TreeMap",
            "custom:roles" : "roles"
          }
        },
        {
          "@class" : "org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy",
          "allowedAttributes" : [ "java.util.ArrayList", [ "roles", "locale", "email" ] ],
          "order": 2
        }
      ]
    ]
  }
}

Attribute Release Policy

Notice that our service definition intentionally does not define any authorized scopes. Typically, defined scopes for a given service definition control and build attribute release policies internally in CAS and the mere definition of authorized scopes for a client application is sufficient in many cases to let CAS formulate the correct attribute/claim release policies. Such attribute release policies allow one to release standard claims, remap attributes to standard claims, or define custom claims and scopes altogether.

In our case above, we are taking advantage of an advanced variation of this configuration to define and use free-form attribute release policies outside the confines of a scope to freely build and release claims/attributes. We are chaining multiple release policies together, allowing CAS to iterate through the chain and collect, cascade, and merge results at every step, in the following sequence:

  1. Collect and release available claims that map to the OpenID Connect profile scope.
  2. Take the custom:roles, released by Amazon Cognito, and remap/rename it to roles instead.
  3. Release roles, locale, and email as ID-token claims to the OpenID Connect application.

So…

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.

Happy Coding,

Misagh Moayyed