Apereo CAS - Linking Accounts with Delegated Authentication


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.

Overview

In the event that CAS is configured to delegate authentication to an external identity provider, it may be necessary to link the received profile from the identity provider to an internal account found in LDAP, SQL databases or any other systems. In this tutorial, we will focus on how to establish the authenticated subject based on this secondary lookup, using an identifier that is provided by the identity provider.

This tutorial specifically focuses on:

Use Case

Our starting position is a CAS server that is configured to hand off the authentication flow to an external identity provider described here. Once the response from the identity provider is validated and a profile response has been collected, CAS will get access to a profile identifier plus a number of attributes that may have been released by the provider depending on the semantics of the protocol in question. In building the authenticated subject and linking that to the SSO session, CAS by default has options to either use what is called a typed id which is translated as [InternalProviderName]+[Separator]+[ProfileId] or simply the profile id itself. This means that when the SSO session is established, the CAS authenticated subject will be communicated to all integrated applications using one of those two options.

Of course, you may run into scenarios where neither id would actually make that much sense. For example, the internal identity may be a seemly pseudorandom integer that would not be all that practical for other applications. The better scenario as an option would be for CAS to simply look up the real record associated with that internal id, (assuming linking between the two records is made available already), and build the principal identifier and attributes according to the data store internal to CAS.

This is tutorial on how to do just that.

Setup

Assuming delegated authentication is configured in CAS using any of the supported identity providers, our job here is put together code that massages the principal construction in CAS once the flow travels back from the identity provider over to CAS. To do this, we are going to take advantage of CAS PrincipalFactory components whose job is to build authenticated subjects, or Principals. Most if not all of authentication strategies in CAS are preconfigured with their own instance of a PrincipalFactory that would know how to translate a authenticated successful response into a Principal object CAS can understand.

So start to prepare CAS with a customized configuration component that would house our specific choice of the PrincipalFactory used in delegation scenarios. Once that is done, take note of the following bean definition posted in Pac4jAuthenticationEventExecutionPlanConfiguration today:

@ConditionalOnMissingBean(name = "clientPrincipalFactory")
@Bean
public PrincipalFactory clientPrincipalFactory() {
    return PrincipalFactoryUtils.newPrincipalFactory();
}

Note how the bean is marked as conditional, meaning it will only be used by CAS if an alternative definition by the same is not found. So, in order for CAS to pick up our own alternative implementation, we are going to provide that bean definition in our own configuration class as such:

@Bean
public PrincipalFactory clientPrincipalFactory() {
    return PrincipalFactoryUtils.newPrincipalFactory();
}
Compile Dependencies
Note that in order for the CAS overlay build to compile our changes and put them to good use, the overlay must be prepared with the required module used during the compilation phase. Otherwise, there will be errors complaining about missing symbols, etc.

Once you have the build compiling correctly, our next task would be to alter the body of our own clientPrincipalFactory bean definition to do what it needs, which is the establishment of the CAS principal based on provided ids, attributes, etc. You can certainly provide your own implementation of PrincipalFactory. What might be easier is if you were given the ability to change the implementation dynamically without having to rebuild CAS every time minor changes are required. To do this, aside from the default implementation of PrincipalFactory, CAS provides a built-in option to externalize all that logic to a Groovy script. The construction of that option would more or less look like this:

@Autowired
private ResourceLoader resourceLoader;

@Bean
public PrincipalFactory clientPrincipalFactory() {
    Resource script = resourceLoader.getResource("file:/etc/cas/config/CustomPrincipalFactory.groovy");
    return PrincipalFactoryUtils.newGroovyPrincipalFactory(script);
}

…and of course, our Groovy script found at /etc/cas/config/CustomPrincipalFactory.groovy would have the following structure:

import org.apereo.cas.authentication.principal.*
import org.apereo.cas.authentication.*
import org.apereo.cas.util.*

def run(Object[] args) {
    def id = args[0]
    def attributes = args[1]
    def logger = args[2]

    return new SimplePrincipal(id, attributes)
}

Now when CAS begins to construct the final authenticated principal, this Groovy script will be invoked to receive the identifier of the received response from the identity provider, any attributes that were submitted and extracted by CAS as a Map as well as a convenient logger object. Next, you can code in additional logic to contact the necessary systems and execute queries based on the id to collect the real record linked to that id or any of the provided attributes and ultimately, return an object of type SimplePrincipal that would carry the authenticated subject and its claims.

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

Related Posts

CAS 5.3.0 RC4 Feature Release

...in which I present an overview of CAS 5.3.0 RC4 release.

Apereo CAS - Identity Impersonation

You do not always have to be you. Allow the Apereo CAS server to allow you to pretend to be another person for fun and profit.

Apereo CAS - Customized Settings

Extend the Apereo CAS server to allow custom configuration properties and settings.

Apereo CAS - Handling Multiple Logout URLs

Extend the Apereo CAS server to allow for multiple logout URLs during SLO operations.

Apereo CAS - Access Strategy External URL Redirects

A quick use case walkthrough where the authentication flow in CAS is to be redirected to a customized external URL if service access is denied.

Nominate awesome fits for the Apereo board

Provoking thoughts about who might serve well on Apereo board.

Apereo CAS - Test-Driving Feature Modules

An overview of how various CAS features modules today can be changed and tested from the perspective of a CAS contributor working on the codebase itself to handle a feature request, bug fix, etc.

Feedback on draft Apereo strategy

Focus on revenue to achieve sustainability. Defer everything else.

Apereo CAS Best [Mal]Practice - Supercharged Overlays

An overview of how a CAS overlay prepped for deployment can tap into internal components, altering logic and behavior for good and evil...but mostly evil.

CAS 5.3.0 RC3 Feature Release

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