Delegated Authentication to Azure AD
Optional Content
This is all optional. If you don't need to delegate authentication to Azure and all of your authentication will be against an on-prem Active Directory or other LDAP/DB, then you can skip this section.
Setup an application in Azure AD
Azure Active Directory needs to have an application registered in order for CAS to delegate authentications to it. This is all done via the Azure AD portal.
- Go to the Azure AD portal.
- Click Enterprise Applications.
- At the top, click New application then Create your own application near the top left.
- Select Integrate any other application you don't find in the gallery (Non-gallery), then give your app a name. This name is, at least by default, user visible (depending on where users look) so I recommend something clear like "Your School Login-Dev", "Your School Login-Test" or "Your School Login" (for prod).
- Click Users and groups on the left. Use this section to add any users or groups who need access - though you may want to start with one or more test users.
- Click Single sign-on on the left, then SAML.
- Copy the App Federation Metadata URL - you'll need this for the DEV_AZURE_METADATA_PATH later.
We're not done here yet - but need to do a few other things before we can finish - notably setting up the CAS side and the metadata there.
Note regarding application names
I'd recommend not having a spaces or crazy special characters in the app name. I have only tested with alphanumeric characters along with hypnens in the client name. I don't know what Azure would allow, but would cause issues with CAS, or CAS clients, etc, but I haven't had a reason to find out. Stick with the basics.
Configure SAML properties in CAS
Add the following settings to roles/cas6/templates/dev-cas.properties.j2 in the cas-overlay-template directory on your Ansible server. As with other sections - we're going to use variables here since some of these properties are sensitive, and we'll leave them as variables here (and the real values will be in the Ansible cas-vault.yml file).
Azure SAML settings
roles/cas6/templates/dev-cas-properties.j2:
cas.authn.pac4j.saml[0].keystorePassword={{ DEV_KEYSTORE_PASSWORD }}
cas.authn.pac4j.saml[0].privateKeyPassword={{ DEV_SAML_KEY_PASSWORD }}
cas.authn.pac4j.saml[0].keystorePath=/etc/cas/config/samlKeystore.jks
cas.authn.pac4j.saml[0].serviceProviderEntityId={{ DEV_SAML_ENTITY_ID }}
cas.authn.pac4j.saml[0].serviceProviderMetadataPath=/etc/cas/config/sp-metadata.xml
cas.authn.pac4j.saml[0].identityProviderMetadataPath={{ DEV_AZURE_METADATA_PATH }}
cas.authn.pac4j.saml[0].clientName={{ DEV_AZURE_APP_NAME }}
cas.authn.pac4j.saml[0].use-name-qualifier=false
# Set the following to match Azure otherwise you're going to have issues with errors on CAS like "Authentication issue instant is too old or in the future"
cas.authn.pac4j.saml[0].maximum-authentication-lifetime=8640000
# Use the following if you ONLY are going to use Azure for authentication and not a local source like on-prem AD
# cas.authn.pac4j.saml[0].autoRedirect=true
Automatic redirect
Once you are done testing - if you ONLY want to delegate authentication (i.e. no local AD or LDAP auth) you may want to use the autoRedirect setting that I have commented out above. This will cause anyone who goes to CAS (either directly or via a redirect from a service) to be automatically redirected to the specified external identity provider.
maximum-authentication-lifetime
Azure has REALLY long lived sessions by default (and you can only change this via conditional access). Without this in place - any user with a long-lived session will get errors unless they completely log out of Office 365/Azure.
One fix is setting the maximum-authentication-lifetime to 90 days as shown above. I don't like this though - but I'm still looking at options. See the following for more info:
- SAML - Authentication issue instant is too old or in the future - not CAS specific but same pac4j core in use.
- Configure authentication session management with Conditional Access
Variable setup
Edit your cas-vault.yml file within roles/cas6/vars/
You'll need to add the following:
DEV_KEYSTORE_PASSWORD: SomeStrongPasswordImSure
DEV_SAML_KEY_PASSWORD: SomeOtherStrongPassword
DEV_SAML_ENTITY_ID: urn:mace:saml:pac4j:org
DEV_AZURE_APP_NAME: Your School-Dev
DEV_AZURE_METADATA_PATH: https://login.microsoftonline.com/<some-tenant-specific-info>/federationmetadata/2007-06/federationmetadata.xml?appid=<some-app-specific-info>
Where to get these values
- DEV_KEYSTORE_PASSWORD: This is a password you choose to protect your SAML keystore
- DEV_SAML_KEY_PASSWORD: This is a password you choose to protect your SAML private key
- DEV_SAML_ENTITY_ID: This is unique and cannot be reused in more than one app in Azure. The 'sample' entity id is urn:mace:saml:pac4j.org. You may want to use something like urn:mace:saml:dev.yourdomain.edu.
- DEV_AZURE_APP_NAME: This is the name you gave the application in step 4 of Setup an application in Azure AD.
- DEV_AZURE_METADATA_PATH: This is from step 7 of Setup an application in Azure AD.
Deploy to ONLY ONE CAS server
You need to deploy this to one CAS server only to start. In order for the files below to be created (if they don't exist already) you have to start CAS - and then visit the CAS page in a browser. If you deploy to more than one it's not a huge deal - but you'll want to make sure the files below are the same in each tier (i.e. all of your prod tier has the same files, all of your dev tier has the same files, etc.).
- samlKeystore.jks
- sp-metadata.xml
- saml-signing-cert-
.crt - saml-signing-cert-
.pem - saml-signing-cert-
.key
Distributing these files via Ansible
Optional of course - but if you don't do it via Ansible you'll have to make sure some other way that these are the same on a tier.
Encrypt the key/pem/keystore files
- Copy the five files listed above to your ansible/roles/cas6/files directory
- rename samlKeystore.jks to samlKeystore-DEV.jks (or some other indication that it's the dev store)
- rename sp-metadata.xml to sp-metadata-DEV.xml (or some other indication that it's the dev store)
- In that directory type the following:
ansible-vault encrypt saml-signing-cert-YourSchoolLogin-DEV.key saml-signing-cert-YourSchoolLogin-DEV.pem samlKeystore-DEV.jks
Create an Ansible task to push those files out
First update main.yml and include a reference to a new sub-task:
roles/cas6/tasks/main.yml:
- include_tasks: debug.yml
- include_tasks: base-cas-config.yml
- include_tasks: cas-ajp-proxy.yml
- include_tasks: service-config.yml
- include_tasks: push-saml-files.yml
**roles/cas6/tasks/push-saml-files.yml
- include_vars: cas-vault.yml
# Note: These are for dev specifically. If we have multiple environments, there's
# will be different keystores, certificates, metadata, etc., for each host.
- name: Ensure saml-signing-cert .crt file (DEV) is up-to-date
ansible.builtin.copy:
src: saml-signing-cert-YourSchoolLogin-DEV.crt
dest: /etc/cas/config/saml-signing-cert-YourSchoolLogin-DEV.crt
mode: 0600
owner: tomcat
group: tomcat
ignore_errors: yes
when: ("login6dev" in inventory_hostname)
notify: restart tomcat
- name: Ensure saml-signing-cert .key file (DEV) is up-to-date
ansible.builtin.copy:
src: saml-signing-cert-YourSchoolLogin-DEV.key
dest: /etc/cas/config/saml-signing-cert-YourSchoolLogin-DEV.key
mode: 0600
owner: tomcat
group: tomcat
ignore_errors: yes
when: ("login6dev" in inventory_hostname)
notify: restart tomcat
- name: Ensure saml-signing-cert .pem file (DEV) is up-to-date
ansible.builtin.copy:
src: saml-signing-cert-YourSchoolLogin-DEV.pem
dest: /etc/cas/config/saml-signing-cert-YourSchoolLogin-DEV.pem
mode: 0600
owner: tomcat
group: tomcat
ignore_errors: yes
when: ("login6dev" in inventory_hostname)
notify: restart tomcat
- name: Ensure sp-metadata.xml (DEV) is up-to-date
ansible.builtin.copy:
src: sp-metadata-DEV.xml
dest: /etc/cas/config/sp-metadata.xml
mode: 0600
owner: tomcat
group: tomcat
when: ("login6dev" in inventory_hostname)
ignore_errors: yes
notify: restart tomcat
- name: Ensure samlKeystore.jks (DEV) is up-to-date
ansible.builtin.copy:
src: samlKeystore-DEV.jks
dest: /etc/cas/config/samlKeystore.jks
mode: 0600
owner: tomcat
group: tomcat
when: ("login6dev" in inventory_hostname)
ignore_errors: yes
notify: restart tomcat
Why the ignore_errors setting?
This is the only place so far that the 'ignore_errors' is used - and it's used for all the tasks here. You may be wondering why?
The reason for this - is that when the CAS6 playbook is first being run - those files will not exist. You'll run this on one server, start up CAS, and go to the CAS login page. That will cause these files (samlKeystore.jks, sp-metadata.xml, and the various saml-signing-certs) to be created. Once you have them created on one host in a tier, you can copy them from that host to your Ansible files directory (and encrypt them if you're going to have them in git)
Rerun the playbook
[chauvetp@ansible templates]$ ansible-playbook ~/ansible/site.yml --ask-vault-pass --limit <your_CAS_server>
Vault password:
Finish Azure config
There's a few remaining things to do in Azure.
Upload CAS metadata to Azure
- Have the sp-metadata.xml file available from CAS then go back to the application you created earlier in Azure (Azure AAD -> Enterprise Applications -> Search for your app)
- Go to the Single sign-on tab
- Click Upload metadata file near the top and navigate to the sp-metadata.xml file then click Add then Save
- Optional: Within the Properties section - you may want to set "Visible to Users" to no.
Environment specific
This is highly dependent on your own environment! In my environment this takes my userPrincipalName (chauvetp@newpaltz.edu) and transforms it to just chauvetp (ExtractMailPrefix takes the part before the @ sign).
If your environment is like ours (and it may not be!) - your cas applications want to see a person's username - not their full email address or userPrincipalName as the 'cas user'. You can do a transformation within Azure to make this happen.
- Go to the Single Sign On section of your app in Azure again (Azure AD -> Enterprise Applications -> Your application).
- In section 2, User Attributes & Claims, click Edit.
- In the Required claim section, click on the existing claim.
- Change Name identifier format from Email address (which is the default in Azure) to just default
- Change Source from Attribute to Transformation
- Set Transformation to ExtractMailPrefix()
- Set Parameter 1 to user.userprincipalname
- Click Add at the bottom, then Save