Since the introduction of Passwordless Authentication in CAS 6.1.x
, a lot of additional and useful improvements are added to make this integration seamlessly integrate with other forms of authentication such as delegation or multifactor. This blog post intends to demonstrate a few advanced variations of passwordless authentication in combination with other authentication flows and forms.
Our starting position is based on the following:
7.1.x
Passwordless Authentication in CAS by default is a form of authentication where passwords are replaced by tokens that expire after a configurable time period. Users are asked for an identifier (i.e. username) which is then used to locate the user record that contains forms of contact such as email and phone number to receive the token. While this works nicely for certain use cases, there are now other variations available where the passwordless user account fetched relevant user repositories can skip the default flow in favor of multifactor or delegated authentication. Furthermore, it is now also possible to, conditionally, skip the default passwordless authentication flow and simply challenge the user for their password as one might traditionally expect.
Passwordless Authn might also be interpreted as WebAuthn support; a specification and API written by the W3C and FIDO that allows identity providers such as CAS to register and authenticate users using public-key cryptography instead of a password. While this behavior can be achieved using certain multifactor provider integrations with CAS that provide WebAuthn support, built-in native support for WebAuthn is not available as of this writing and may become available in future releases.
Once you have decorated the CAS WAR Overlay with the proper extension module, you will need to adjust your CAS configuration (i.e. cas.properties
file) to tune the feature for the following:
How should user records and contact information be found, given an identifier?
To keep things simple for this tutorial, we are going to use a Groovy script to locate our passwordless accounts:
cas.authn.passwordless.accounts.groovy.location=file:/etc/cas/config/PasswordlessAccounts.groovy
…and the Groovy script may be designed as:
def run(Object[] args) {
def request = args[0]
def logger = args[1]
logger.info("Locating user record for user $request.username")
/*
* Query relevant data sources to fetch
* the passwordless account record...
*/
def account = new PasswordlessUserAccount()
account.setUsername("casuser")
/*
* Modify the account as appropriate...
*/
return account
}
A passwordless account can be optionally tagged to use the traditional authentication flow that typically challenges the user for their password. To do this, the account needs to be modified as such:
...
account.setRequestPassword(true)
...
In doing so, CAS will skip the default passwordless authentication flow in favor of its default primary authentication strategy where you might see this:
In another variation, the passwordless account can skip its default flow in favor of delegated authentication. To do so and with the assumption that delegated authentication is turned on in CAS, we can tag the account as one who’s eligible for delegated authentication via:
...
account.setDelegatedAuthenticationEligible(true)
...
This requires a separate decision to select the identity provider from the list of those that are available and configured in CAS. To do this, we can design a small Groovy script tasked to decide and select the appropriate identity provider for our passwordless user:
cas.authn.passwordless.delegatedAuthenticationSelectorScript.location= \
file:/etc/cas/config/PasswordlessDelegated.groovy
…and the selection script would be:
def run(Object[] args) {
def user = args[0]
def clients = (Set) args[1]
def httpServletRequest = args[2]
def logger = args[3]
logger.info("Choose identity provider for $user")
/*
* Pick an identity provider from
* the list of clients available
*/
return clients[0]
}
In another variation, the passwordless account can skip its default flow in favor of multifactor authentication using a multifactor provider that is found configured and available in CAS. In doing so and much like other variations, the account must be tagged as one eligible for multifactor authentication:
...
account.setMultifactorAuthenticationEligible(true)
...
Once the account is deemed eligible for a multifactor authentication flow, it is processed by the collection of multifactor authentication triggers to select the appropriate provider. To keep things simple, let’s say we would want to require MFA for users with an attribute category
whose value(s) matches a pattern of student
or admin
.
cas.authn.mfa.triggers.principal.global-principal-attribute-name-triggers=category
cas.authn.mfa.triggers.principal.global-principal-attribute-value-regex=student|admin
Let’s also make sure our demo passwordless account carries that relevant attribute:
...
def attributes = Map.of("category", List.of("student", "admin"))
account.setAttributes(attributes)
...
With the above configuration, the account is processed by CAS multifactor authentication triggers to route to the appropriate multifactor authentication flow.
Duo Security can also act as a passwordless account store. Once enabled, user accounts that are found and registered with Duo Security with a valid email address and phone number will receive a push notification from Duo Security and are able to login to CAS without the need for a password:
cas.authn.mfa.duo[0].duo-secret-key=...
cas.authn.mfa.duo[0].duo-integration-key=...
cas.authn.mfa.duo[0].duo-api-host=...
cas.authn.mfa.duo[0].passwordless-authentication-enabled=true
Passwordless authentication can be selectively controlled for specific applications. By default,
all services and applications are eligible for passwordless authentication. If you wish to disable the flow for a particular application,
modify the application policy and indicate your own passwordlessPolicy
construct:
{
"@class": "org.apereo.cas.services.CasRegisteredService",
"serviceId": "^https://app.example.org",
"name": "App",
"id": 1,
"passwordlessPolicy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServicePasswordlessPolicy",
"enabled": false
}
}
If you have questions about the contents and the topic of this blog post, or if you need additional guidance and support, feel free to send us a note and ask about consulting and support services.
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.
Monday-Friday
9am-6pm, Central European Time
7am-1pm, U.S. Eastern Time
Monday-Friday
9am-6pm, Central European Time