SSO Configuration In Keycloak for Folio

Introduction

This document describes how to setup Keycloak with one or more SAML SSO Identity Provider (IdP).

Scope

  • Setup of SAML SSO in Keycloak for Folio via the Keycloak Administrative Console
  • Keycloak is very flexible in regards to how SSO is configured.  This guide focuses on providing the behavior Folio users are already accustomed to using.  Other behavior/flows are out of scope for this guide.
  • While the necessary steps can most likely be performed via API calls, that is out of scope for this guide.
  • Keycloak v22.x or v23.x.  The steps documented here have not been tested/verified on other versions.
  • Creation/management of users in Folio is out of scope for this guide.

Prerequisites

  • Access to the Keycloak Administrative Console.  
  • A valid, active Folio user which has a corresponding user record in Keycloak.  
  • Administrative privileges on the Identity Provider side for metadata exchange

Setup Procedure

Authentication and Tenant/Realm Selection

  1. Log into the Keycloak Administrative Console
  2. Choose the appropriate realm (tenant) from the drop-down menu in the top left corner.  This is the tenant you wish to setup SSO for.

Flow Creation

  1. Select "Authentication" in the main navigation pane on the left-hand side of the screen.
  2. On the "Flows" tab, you will need to create a new flow which matches existing users to the assertions in the SAML response.  Click the "Create flow" button.
  3. Fill in the form and submit:
    1. Give your flow a name, e.g. "Detect and Set Existing User". 
    2. Optionally provide a description of the flow, e.g. "Flow intended to be used for SAML SSO.  Detects and sets the existing user"
    3. Choose "Basic flow" from the drop-down.
  4. Once the flow has been created, you'll arrive at a wizard to help add steps to the flow.  Fortunately this flow is simple and contains only two executions: 
    1. Click the "Add execution" button
    2. Scroll down and select "Detect existing broker user", then click the "Add" button
    3. Click the "Add step" button
    4. Select the "Automatically set existing user", then click the "Add" button
    5. Choose "Required" from the "Requirement" drop-down for both steps.

The Flow should look something like this:

Add an Identity Provider

  1. Select "Identity Providers" in the main navigation pane on the left-hand side of the screen.
  2. Click "SAML v2.0" under the "User-defined" heading.
    1. NOTE:  if you already have at least one IdP configured, the UX is slightly different.  In this case you'll want to Click the "Add Provider" button.  A drop-down menu will appear.  Select "SAML v2.0".
  3. Fill in "General settings":
    1. Give this IdP an "Alias", e.g. "ssocircle"
    2. Provide a "Display name" for this IdP, e.g. "SSO Circle"
      1. NOTE:  this is what the end user will see as an option on the login page.
    3. Specify a "Display order".  When multiple IdPs are configured, this specifies which order they appear in on the login screen.  Descending order starting from 0.
      1. NOTE:  if only using a single IdP, this doesn't matter.
    4. Note the "SAML 2.0 Service Provider Metadata" link.  You will need this later on.
  4. Fill in the "SAML settings":
    1. The default values for "Service provider entity ID" and "User Entity descriptor" (on) should be left as-is.
    2. Enter the URL pointing to the IdP metadata in the "SAML entity descriptor" field, e.g. https://idp.ssocircle.com/meta-idp.xml
  5. Click the "Add" button
    1. This will pull down the IdP metadata and populate several fields in your IdP configuration
  6. You should now see many more fields available in the Identity Provider settings.  For the most part you can leave these as-is.  However, there are a few that are of interest:
    1. "SAML settings" → "Allow create":  turn this off.
    2. "Advanced settings" → "First login flow":  select the flow you configured earlier, e.g. "Detect and Set Existing User"
    3. Don't forget to save any changes you make before moving on by clicking the "Save" button.

NOTE: For additional information, including setting descriptions, see https://www.keycloak.org/docs/latest/server_admin/#saml-v2-0-identity-providers

IdP Mapper Creation

Now that we have the Identity Provider setup, we need to configure how Keycloak will match users to the information in the SAML response.  For this we need to create a "mapper" for the identity provider.  This part of the guide is less prescriptive, and more of an example of how it could be setup.  Additional examples/recipes will be provided later in this document.

  1. Select "Identity Providers" in the main navigation pane on the left-hand side of the screen.
  2. Click on the provider you created
  3. Select the "Mappers" tab at the top of the "Provider details" pane.
  4. Click the "Add mapper" button
  5. Fill in the form:
    1. Give the mapper a "Name", e.g. "external_system_id"
    2. "Sync mode override":  "Force"
    3. "Mapper type":  "Attribute Importer"
    4. "Attribute Name":  "Subject NameID"
      1. This will select the SAML Response's Subject → NameID property 
    5. "Friendly Name":  <leave blank>
    6. "Name Format":  "ATTRIBUTE_FORMAT_BASIC"
    7. "User Attribute Name":  "external_system_id"
      1. This will try to match the value specified in "Attribute Name" to the "external_system_id" attribute on the Keycloak user record.
  6. Click "Save"

Complete Metadata Exchange

We're already imported the IdP metadata into Keycloak (see "Add Identity Provider" steps 4 & 5).  Now we need to let the IdP know about the SP (Keycloak).  The exact steps here will differ from IdP to IdP, but the general idea is the same.  Grab the SP metadata from Keycloak and enter it, along with some other information into the IdP.  Below is an example of how this is done using the SSO Circle IdP.

NOTE:  This guide assumes you already have an account with SSO Circle.  

  1. Goto https://www.ssocircle.com/ and login
  2. Click on "Manage Metadata" from the navigation bar on the left.
  3. Click "Add new Service Provider"
  4. Fill in the form:
    1. "Enter the FQDN of the ServiceProvider":  Enter the URL of your Folio UI, e.g. folio-snapshot.dev.folio.org 
    2. "Attributes sent in assertion":  Check all boxes 
    3. "Insert the SAML Metadata information of your SP":  You'll need to get the SP metadata from Keycloak. 
      1. See the "Add and Identity Provider" step 3d.  
      2. You can get this info at any time from Keycloak:  "Identity providers" → (select your provider) → "SAML 2.0 Service Provider Metadata" under "General Settings" → "Endpoints".
      3. The URL is also public and deterministic:  https://<keycloak host>/realms/<realm>/broker/<provider name>/endpoint/descriptor, e.g. https://keycloak-foo.int.aws.folio.org/realms/diku/broker/ssocircle/endpoint/descriptor 
  5. Click "Submit"

Test It!

If all previous steps were executed, and prerequisites are satisfied, you should be ready to test our SSO authentication into Folio.

  1. Go to your Folio UI URL
  2. Below the "Log in" button you should see your identity provider(s) listed.
    1. See screenshot below
  3. Click on the IdP you want to use
    1. You should be taken to the IdP and prompted for credentials (if you don't already have a session)
  4. Upon successful authentication, you should be directed back to Folio
  5. Success!

More on IdP Mappers

TODO:  add additional info, examples/recipes, etc.

Additional Documentation

Example Assertion Section of SAML Response

It may be helpful to decode a SAML response and look at the information in the Assertions section when configuring your IdP Mapper(s).

 Click here to expand...
  <saml:Assertion
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="s205af4738466b18f710cd030c5e51a5e3e28241bf"
    IssueInstant="2024-01-29T21:55:08Z"
    Version="2.0">
    <saml:Issuer>https://idp.ssocircle.com</saml:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
    <saml:Subject>
      <saml:NameID
        Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
        NameQualifier="https://idp.ssocircle.com"
        SPNameQualifier="https://keycloak-foo.int.aws.folio.org/realms/diku">LxyImDFLVb4jnlOayJIybA77mZw6</saml:NameID>
      <saml:SubjectConfirmation
        Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData
          InResponseTo="ID_54c25fe7-3f82-43c0-8303-39ac60944365"
          NotOnOrAfter="2024-01-29T22:05:08Z"
          Recipient="https://keycloak-foo.int.aws.folio.org/realms/diku/broker/ssocircle/endpoint" />
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions
      NotBefore="2024-01-29T21:45:08Z"
      NotOnOrAfter="2024-01-29T22:05:08Z">
      <saml:AudienceRestriction>
        <saml:Audience>https://keycloak-foo.int.aws.folio.org/realms/diku</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AuthnStatement
      AuthnInstant="2024-01-29T21:54:59Z"
      SessionIndex="s224836c02da9471203a3dfb73fdb0746d35d1f301">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
    <saml:AttributeStatement>
      <saml:Attribute
        Name="EmailAddress">
        <saml:AttributeValue
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:type="xs:string">hsimpson@gmail.com</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute
        Name="UserID">
        <saml:AttributeValue
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:type="xs:string">hsimpson</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute
        Name="FirstName">
        <saml:AttributeValue
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:type="xs:string">Homer</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute
        Name="LastName">
        <saml:AttributeValue
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:type="xs:string">Simpson</saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>
  </saml:Assertion>

Recipe (Email):

Use the "EmailAddress" attribute from the SAML response, to match the "email" attribute on the Keycloak user record

  • Give the mapper a "Name", e.g. "email"
  • "Sync mode override":  "Force"
  • "Mapper type":  "Attribute Importer"
  • "Attribute Name":  "EmailAddress"
    1. This will select the SAML Response's EmailAddress attribute 
  • "Friendly Name":  <leave blank>
  • "Name Format":  "ATTRIBUTE_FORMAT_BASIC"
  • "User Attribute Name":  "email"

NOTE: In order for this to work, we need to also set the following on the main "Provider Details" form:

  • Principal type:  "Attribute [Name]"
  • Principal attribute:  "EmailAddress"

Recipe (externalSystemId):

Use the "Subject NamdID" from the SAML response, to match the "external_system_id" attribute on the Keycloak user record

  • Give the mapper a "Name", e.g. "external_system_id"
  • "Sync mode override":  "Force"
  • "Mapper type":  "Attribute Importer"
  • "Attribute Name":  "Subject NameID"
    1. This will select the SAML Response's Subject → NameID (Hint: in the example SAML response above this value is LxyImDFLVb4jnlOayJIybA77mZw6)
  • "Friendly Name":  <leave blank>
  • "Name Format":  "ATTRIBUTE_FORMAT_BASIC"
  • "User Attribute Name":  "external_system_id"

N.B.  As of the external_system_id user attribute is not currently set/migrated from user records in Folio to Keycloak.  However, there are plans to add this soon.  Keep in mind that you can always manually add/set arbitrary user attributes in Keycloak for testing purposes.

Tips/Tricks

This section provides some tips/tricks which may or may not be helpful to you, based on various factors.

I can't find my Subject NameID

This one may be specific to SSOCircle...  In some cases I've seen that the SAML response doesn't include the expected information, including assertions and even the Subject's NameID, until you do something like the following:

  1. In step 6 of "Add an Identity Provider", you specify "Allow Create": "off" and specify the "Detect and Set Existing User" flow you created earlier in the process.  The trick here is to basically undo those changes, setting "Allow Create": "on", and change the flow to "First broker login".  
  2. Attempt to login again.  
  3. Keycloak will probably present you with a form for creating a new user since one couldn't be found.  The form will be prepopulated with the Subject's NameID.  Copy this for later use.
  4. Repeat step 6 of "Add an Identity Provider" to turn Allow Create off and specify your Detect and Set Existing User flow.
  5. Now when you login, if you inspect the SAML response, you should see the assertions and the Subject's NameID. 

Explore options for the automatic creation of Identity Provider links in Keycloak (SSO)

Custom extension to map folio user to identity provider's subject name id 

Currently, Keycloak cannot map SubjectNameId to any custom user attributes; it can only compare it with a username or email address. That's why we need to build our own custom external provider to map the external_system_id attribute of the Keycloak user to the SubjectNameId.

POC extension implementation for detecting Keycloak users by custom attribute

Link on the code https://github.com/folio-org/folio-keycloak/tree/KEYCLOAK-14-investigate-options-for-automatic-creation-of-identity-provider-links-in-keycloak-sso

The extension has been implemented to find the user based on a custom attribute. The attribute name is currently hardcoded but needs to be made configurable. The rest can be used as is. 

How to install an extension into Keyclok 

To add extensions, you need to build a .jar file. An example can be found as an outcome from the POC. Place this file into the libs folder of the folio-keycloak project. The rest will happen automatically."


How to configure the authorization flow to use the new extension

To detect Folio users, a custom flow must be created using two steps. The first step is our custom extension, which finds an existing user by the externalId attribute, and the second step is the default step to link the identity provider to the user.

Open Questions

  • TBD
  • Is there a way for Folio admins (who don't have access to Keycloak Administrative Console) to see some basic info? e.g. IdP Mappers - which SAML attr maps to which Folio user field