EUREKA-72: Investigate options consortia UI bundles
- 1 Spike Overview
- 2 Background
- 3 Problem Statement
- 4 Scope
- 5 Proposed Solutions
- 5.1 Option 1: Custom JavaScript Functions with Login Form Customization
- 5.1.1 Overview
- 5.1.2 Sequence Diagram
- 5.1.3 Implementation Details
- 5.1.4 Pros
- 5.1.5 Cons
- 5.1.6 JavaScript functions example
- 5.1.7 Summary
- 5.2 Option 2: Keycloak Custom Authenticator for All Tenants
- 5.2.1 Overview
- 5.2.2 Sequence Diagram
- 5.2.3 Implementation Details
- 5.2.4 Pros
- 5.2.5 Cons
- 5.2.6 Authenticator implementation example
- 5.2.7 Summary
- 5.3 Option 3: Hybrid Approach with Simplified Custom Authenticator and JavaScript Redirection
- 5.3.1 Overview
- 5.3.2 Sequence Diagram
- 5.3.3 Implementation Details
- 5.3.4 Pros
- 5.3.5 Cons
- 5.3.6 JavaScript Code Example
- 5.3.7 Keycloak Custom Authenticator Example
- 5.3.8 Summary
- 5.4 Option 4: Custom Keycloak Authenticator (FolioEcsUsernamePasswordFormV2)
- 5.4.1 Overview
- 5.4.2 Sequence Diagram
- 5.4.3 Implementation Details
- 5.4.4 Pros
- 5.4.5 Cons
- 5.4.6 Keycloak Custom Authenticator Example
- 5.4.7 Summary
- 5.1 Option 1: Custom JavaScript Functions with Login Form Customization
- 6 Prerequisites
- 7 Conclusion
Spike Overview
https://folio-org.atlassian.net/browse/EUREKA-72
Objective: To determine an approach for implementing Automatic Tenant Selection in FOLIO, allowing users to log into their home tenant without being prompted to select it manually, thereby enhancing the user experience across consortia institutions.
Background
FOLIO is a collaborative, open-source library services platform that enables multiple institutions within a consortium to use the same application. Each institution is represented as a separate tenant within FOLIO, and staff users are defined within their institution's tenant context. Users have capabilities assigned to them that define the actions they can perform within their tenant.
Currently, if a user from Institution A (Tenant A) needs to perform actions in Institution B (Tenant B), a shadow user is created in Tenant B with the necessary capabilities. The user must switch affiliations within the FOLIO application to change the tenant context for their session.
Keycloak is used as the Identity and Access Management (IAM) system for FOLIO, with each tenant represented as a separate Realm. Real FOLIO users correspond to Keycloak users with credentials, allowing them to authenticate and log in. Shadow users in FOLIO correspond to Keycloak users without credentials and cannot be used for login operations. Capabilities assigned in FOLIO are reflected as permissions granted to Keycloak users.
The current FOLIO implementation includes:
Single UI Bundle: Users log into a central tenant and switch affiliations as needed.
Manual Tenant Selection: Users select their tenant from a drop-down menu and are directed to the appropriate UI bundle for their tenant, authenticating in the Keycloak Realm associated with their tenant.
The need has arisen for an Automatic Tenant Selection feature where users are not prompted to select their tenant. Instead, the system detects the user's home tenant upon login and directs them accordingly.
Problem Statement
The spike aims to address the challenge of implementing Automatic Tenant Selection in FOLIO. The goal is to enable users to see a unified login screen and, upon authentication, be automatically directed to their home tenant without manual selection. This requires a method to detect the user's home tenant based on the user’s credentials and adjust the authentication flow accordingly within Keycloak and FOLIO.
Scope
In Scope
Investigate methods to automatically detect a user's home tenant during the login process.
Explore configurations or extensions in Keycloak to support automatic tenant selection based on user credentials.
Assess how automatic tenant selection affects authentication flow, session management, and capability assignments in FOLIO.
Ensure the proposed solution aligns with FOLIO's architecture and maintains security and compliance standards.
Out of Scope
Implementation or deployment of the automatic tenant selection feature.
Modifications to existing tenant switching mechanisms or shadow user management.
User interface design changes beyond the login screen.
Proposed Solutions
Two possible options have been identified to implement Automatic Tenant Selection in FOLIO:
Custom JavaScript Functions with Login Form Customization
Keycloak Custom Authenticator for All Tenants
Hybrid Approach with Simplified Custom Authenticator and JavaScript Redirection
Custom Keycloak Authenticator for the Central realm
Each option is analyzed in detail below, including high-level sequence diagrams, implementation details, and the pros and cons.
Option 1: Custom JavaScript Functions with Login Form Customization
Overview
This option involves customizing the Keycloak login form using JavaScript to determine the correct tenant (realm) based on the user's input (username). The process includes intercepting the login request, retrieving the user's tenant information by communicating with mod-login-keycloak, and then redirecting the user to authenticate against the appropriate tenant realm.
Sequence Diagram
The high-level sequence diagram for this option is as follows:
Implementation Details
Customize Keycloak Login Page
JavaScript Injection: Modify the Keycloak login page to include custom JavaScript code that executes when the user submits their credentials.
Intercept Submission: The JavaScript code intercepts the form submission event to prevent the default behavior.
Retrieve User's Tenant
HTTP Request to
mod-login-keycloak: The JavaScript code sends an HTTP POST request tomod-login-keycloak, passing the entered username and password.Authentication Check:
mod-login-keycloakcommunicates with Keycloak to validate the user's credentials.Tenant Resolution: Upon successful validation,
mod-login-keycloakdetermines the user's home tenant.
Redirect to Tenant's Login Page
Construct Tenant URL: The JavaScript code constructs the URL for the tenant's login page based on the tenant information received.
Redirection: The JavaScript function requests the tenant-specific login page. It does not render it for the user but sends the authentication Post request to Keycloak.
Authenticate Against Tenant Realm
Submit Credentials: The user's credentials are submitted to the tenant realm's for authentication.
Validation: Keycloak validates the credentials within the tenant realm.
Authentication Success: Upon successful authentication, the user is redirected to the FOLIO landing page.
Pros
User Experience
Unified Login Screen: Users access a single login screen without the need to select their tenant manually.
Seamless Redirection: Redirection to the tenant realm happens transparently to the user.
Implementation Simplicity
Minimal Backend Changes: The logic is handled primarily on the client side through JavaScript.
No Custom Authenticator Required: Avoids the complexity of developing and maintaining a custom Keycloak authenticator.
Flexibility
Easy Updates: Changes to tenant resolution logic can be applied by updating the JavaScript code.
Cons
Security Concerns
Credential Exposure Risk: User credentials are handled in the browser and sent over HTTP to
mod-login-keycloakbefore authentication, increasing the risk of interception.Client-Side Vulnerabilities: JavaScript code can be manipulated or disabled by users or malicious actors.
Man-in-the-Middle Attacks: Increased risk if HTTPS is not strictly enforced.
mod-login-keycloakhas to expose unathorized API that can be used for hacking
Maintenance Overhead
Browser Compatibility: Ensuring the JavaScript functions correctly across all browsers and devices.
Debugging Difficulty: Client-side issues can be harder to trace and resolve.
mod-login-keycloakextra complexity:mod-login-keycloakmust be improved to handle credentials validation and realm resolution.
Reliance on Client-Side Logic
User Manipulation: Technically savvy users could alter the JavaScript to change the authentication flow.
Accessibility Issues: Users with JavaScript disabled or using assistive technologies may encounter problems.
Complex Error Handling
User Feedback: Providing meaningful error messages is more complex when multiple steps are involved client-side.
JavaScript functions example
function get_realm_link_by_username() {
const username = document.getElementById("username").value;
// Determine which link to return based on the username content
// This is just a stub. The HTTP call to mod-login-keycloak must be done here instead.
if (username.includes("coltest")) {
const link = document.getElementById("social-college");
return link ? link.href : null;
} else if (username.includes("unitest")) {
const link = document.getElementById("social-university");
return link ? link.href : null;
}
// Return null if no matching link is found
return null;
}
async function process_complex_realm_login() {
const href = get_realm_link_by_username();
if (!href) {
console.error("No matching realm link found for username.");
return;
}
try {
// Perform a GET request to the resolved href
const response = await fetch(href);
// Check if the response is OK (status code 200-299)
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
// Parse the response as text
const result = await response.text();
// Create a new DOM parser to convert the HTML string to a document object
const parser = new DOMParser();
const doc = parser.parseFromString(result, "text/html");
// Extract the action attribute from the form in the parsed document
const fetchedFormAction = doc.querySelector("#kc-form-login")?.action;
// Check if the action was found and apply it to the current form, then submit
if (fetchedFormAction) {
const currentForm = document.getElementById("kc-form-login");
currentForm.action = fetchedFormAction; // Set the current form's action
currentForm.method = "post"; // Set the current form's method to POST.
// Submit the form programmatically
currentForm.submit();
} else {
console.error("Form action not found in the fetched content.");
}
} catch (error) {
// Log any errors to the console
console.error("Error fetching login link:", error);
}
}
Summary
While this option offers a quick way to implement Automatic Tenant Selection with minimal backend changes, it introduces significant security risks and maintenance challenges due to its reliance on client-side scripting and handling of user credentials in the browser.
Option 2: Keycloak Custom Authenticator for All Tenants
Overview
This option involves developing a custom Keycloak Authenticator (e.g., FolioAuthenticator) and configuring Keycloak to use this authenticator across all realms configured for consortia and institutional tenants. The custom authenticator handles tenant detection and authentication flow server-side, enhancing security and centralizing the logic.
Sequence Diagram
The high-level sequence diagram for this option is as follows:
Implementation Details
Develop Custom Authenticator (
FolioAuthenticator)Server-Side Logic: Implement the authenticator using Keycloak's Service Provider Interface (SPI).
Tenant Detection: Incorporate logic to determine the user's home tenant based on the username or other identifiers and configured identity providers.
Encrypted Data Handling: Prepare and handle encrypted custom data containing credentials and timestamp for secure transmission between realms.
Configure Keycloak Realms
Consortia Realm: Apply the
FolioAuthenticatorto the central consortia realm.Institutional Realms: Apply the same authenticator to each institutional tenant realm.
Authentication Flow: Modify the authentication flow in Keycloak to include the custom authenticator.
Authentication Flow
Initial Login Request: User submits credentials to the consortia realm.
Tenant Resolution: The custom authenticator checks if the username exists and retrieves the user's identity provider (tenant realm).
Redirect Handling: If the user's identity provider is found, the authenticator prepares encrypted custom data and redirects the user to the tenant realm's login page.
Fallback Mechanism: If the identity provider is not found, proceed with default authentication in the consortia realm.
Tenant Realm Authentication
Data Decryption: The tenant realm's custom authenticator decrypts the received custom data.
Credential Validation: The tenant realm validates the user's credentials.
Authentication Success: User is authenticated and redirected to the FOLIO landing page.
Pros
Enhanced Security
Server-Side Credential Handling: Credentials are processed within Keycloak servers, reducing exposure.
Encrypted Data Transmission: Sensitive information is encrypted when transmitted between realms.
Reduced Attack Surface: Less reliance on client-side scripting minimizes vulnerabilities.
Centralized Logic
Consistency Across Realms: The custom authenticator standardizes authentication flow for all tenants.
Easier Maintenance: Updates to authentication logic are centralized.
Improved Performance
Streamlined Authentication: Fewer external calls and redirections improve login speed.
Scalable Solution: Better suited for handling a large number of tenants.
User Experience
Seamless Login Flow: Users experience a smooth login process without unnecessary redirects or prompts.
Fallback Authentication: Supports default authentication if tenant detection fails.
Cons
Development Complexity
Custom Authenticator Development: Requires in-depth knowledge of Keycloak's SPI and authentication mechanisms.
Testing and Validation: Extensive testing is needed to ensure reliability across different scenarios.
Maintenance Overhead
Version Compatibility: The custom authenticator must be maintained with Keycloak updates.
Configuration Management: All realms must be correctly configured to use the custom authenticator. This configuration must be made transparently during the Consortia configuration. (mgr-tenants? mod-consortia-keycloak? which one should be responsible for that?)
Deployment Challenges
Rollout Strategy: Deploying changes across multiple realms requires careful planning.
Error Handling Complexity: Properly managing authentication errors across realms can be complex.
Potential Single Point of Failure
Authenticator Dependency: Issues with the custom authenticator could affect authentication for all users.
Authenticator implementation example
The authenticator implementation example can be found here:
https://github.com/folio-org/folio-keycloak-plugins/tree/feature/ecs-folio-authenticator/ecs-folio-authenticator
Summary
This option offers a secure, server-side solution that centralizes authentication logic within Keycloak, enhancing performance and scalability. However, significant development effort and careful maintenance are required to ensure compatibility with Keycloak updates and realm configurations.
Option 3: Hybrid Approach with Simplified Custom Authenticator and JavaScript Redirection
Overview
This hybrid option combines server-side tenant resolution with client-side redirection to implement Automatic Tenant Selection in FOLIO. It utilizes a simplified custom Keycloak Authenticator that only resolves the user's home tenant (realm) based on the provided username and does not handle credential validation or generate encrypted data. The Authenticator either authenticates the user if they belong to the current realm or redirects them to their home tenant's login page. Client-side JavaScript on the login page manages redirections transparently, providing a seamless user experience.
Sequence Diagram
Diagram Explanation
Actors and Participants:
Folio User: The end-user attempting to log in.
Browser: The user's web browser executing JavaScript and handling HTTP requests/responses.
Keycloak Consortia Realm (
kc_cr): The central Keycloak realm with the simplified custom Authenticator configured.Keycloak Tenant Realm (
kc_tr): The user's home tenant realm in Keycloak.
Flow Breakdown:
The user accesses the unified login page.
The browser displays the login page.
The user enters their credentials and submits the form.
JavaScript intercepts the form submission and sends an AJAX POST request with the username to the consortia realm (
kc_cr).The simplified custom Authenticator in the consortia realm processes the username to determine the user's home tenant realm.
The Authenticator responds:
If the user belongs to the consortia realm, it proceeds with authentication.
If the user belongs to a different tenant realm, it responds with a redirect to the appropriate realm's login page.
JavaScript in the browser handles the redirect seamlessly by updating the form action and submitting the form to the tenant realm (
kc_tr).The user is authenticated in their home tenant realm.
Upon successful authentication, the user is redirected to their FOLIO landing page.
Implementation Details
Client-Side JavaScript
Event Listener: Adds an event listener to intercept the login form submission.
Form Data Collection: Collects the username input by the user.
AJAX Request: Sends an asynchronous POST request to the consortia realm's login endpoint with the username.
Response Handling:
If the response indicates that the user belongs to the consortia realm, the JavaScript proceeds with the authentication flow.
If the response is a redirect to the tenant realm, the JavaScript updates the form action to the tenant realm's login endpoint and submits the form.
Simplified Custom Keycloak Authenticator (Server-Side)
Consortia Realm Authenticator:
Processes the username to determine the user's home tenant realm.
If the user belongs to the consortia realm:
Proceeds with the standard authentication process (validating credentials).
If the user belongs to a different tenant realm:
Responds with a redirect to the tenant realm's login page.
Tenant Realm Authentication:
The tenant realm handles authentication using its standard process.
No custom Authenticator is required in the tenant realm.
Security Measures
Server-Side Credential Handling: Credentials are only processed on the server side, minimizing exposure.
Reduced Client-Side Logic: JavaScript handles redirection without processing credentials.
Pros
Enhanced Security
Server-Side Credential Validation: Credentials are validated on the server side, reducing exposure risk.
No Credential Handling in JavaScript: Client-side code does not process or transmit credentials.
Improved User Experience
Seamless Redirection: JavaScript manages redirections transparently, providing a smooth login process.
Unified Login Screen: Users access a single login page without manual tenant selection.
Simplified Implementation
Simplified Authenticator: Reduces complexity by only resolving the tenant realm without handling credentials or encryption.
No Custom Data Encryption Needed: Eliminates the need for handling encrypted data between realms.
Flexibility
Centralized Tenant Resolution: Tenant determination logic is centralized in the consortia realm.
Ease of Updates: Changes to tenant resolution logic are confined to the simplified Authenticator.
Cons
CORS Configuration Requirement
Cross-Origin Resource Sharing (CORS): Must configure CORS between the FOLIO landing page and Keycloak login form to allow JavaScript redirection using
window.location.href = xhr.responseURL;.Implications:
Security Considerations: Configuring CORS requires careful management to avoid exposing the application to cross-origin attacks.
Additional Complexity: Increases setup and maintenance overhead to ensure proper CORS policies are in place.
Browser Behavior: Inconsistent or incorrect CORS configuration may lead to login failures or inconsistent behavior across different browsers.
Development Effort
Custom Authenticator Development: Requires development and maintenance of the simplified Keycloak Authenticator.
JavaScript Maintenance: Client-side code needs to be maintained and tested across browsers.
Configuration Complexity
Realm Configuration: The consortia realm must be configured to use the simplified Authenticator.
Deployment Coordination: Synchronizing updates between server-side and client-side components.
Potential Error Handling Challenges
Redirection Failures: If JavaScript fails to handle redirects correctly, users may encounter login issues.
Fallback Mechanisms: Ensuring proper error messages and fallback options in case of authentication failures.
JavaScript Code Example
Keycloak Custom Authenticator Example
Summary
This hybrid approach leverages a simplified custom Authenticator in the consortia realm to resolve the user's home tenant based on the username. It does not handle credential validation or encrypted data transmission, reducing complexity while maintaining security. Client-side JavaScript manages redirections seamlessly without processing credentials, providing a unified and user-friendly login experience.
By centralizing tenant resolution and minimizing client-side logic, this option offers a secure and scalable solution for Automatic Tenant Selection in FOLIO. It balances implementation effort with the benefits of enhanced security and improved user experience.
Option 4: Custom Keycloak Authenticator (FolioEcsUsernamePasswordFormV2)
Overview
This option involves developing a custom Keycloak Authenticator (FolioEcsUsernamePasswordFormV2) that dynamically determines the realm to be used for user authentication based on the provided credentials. The Authenticator processes the authentication request within the current realm if the user belongs to it. If the user belongs to a different realm (linked through an Identity Provider), the Authenticator programmatically authenticates the user against that realm without redirecting the user or requiring any client-side JavaScript modifications.
Key Characteristics:
Server-Side Processing: All logic is handled on the server side within Keycloak.
No Login Form Customization: Does not require any changes to the login page or client-side JavaScript.
Enhanced Security: Prevents intruders from deducing user-realm associations, as no redirection or realm information is exposed to the client.
Seamless User Experience: Users experience a unified login process without manual tenant selection or noticeable redirections.
Sequence Diagram
Diagram Explanation
Actors and Participants:
Folio User: The end-user attempting to log in.
Browser: The user's web browser.
Keycloak with Custom Authenticator (
kc_custom): Keycloak realm configured with theFolioEcsUsernamePasswordFormV2Authenticator.Identity Provider Realm (
kc_idp): The user's home tenant realm or external Identity Provider.
Flow Breakdown:
The user accesses the unified login page.
The browser displays the login page.
The user enters their credentials and submits the form.
The browser sends the authentication request to Keycloak (
kc_custom).The custom Authenticator in Keycloak processes the credentials:
Identifies the realm associated with the user.
If the user belongs to the current realm, it proceeds with authentication.
If the user belongs to a linked realm or Identity Provider, it programmatically authenticates the user against that realm.
Upon successful authentication (either locally or against the Identity Provider), Keycloak returns the authentication response to the client.
The user is redirected to their FOLIO landing page.
Implementation Details
Custom Keycloak Authenticator (FolioEcsUsernamePasswordFormV2)
Purpose: To determine the correct realm for user authentication and handle the authentication process accordingly without exposing realm information to the client.
Functionality:
Realm Identification: Based on the username provided, the Authenticator checks if the user exists in the current realm or is associated with a federated Identity Provider.
Local Authentication: If the user belongs to the current realm, it proceeds with standard password validation.
Programmatic Authentication Against Linked Realm:
If the user is linked to an Identity Provider, the Authenticator programmatically performs authentication against that Identity Provider using the provided credentials.
Utilizes the OAuth 2.0 Resource Owner Password Credentials Grant to obtain an access token from the Identity Provider.
Determines authentication success based on the response from the Identity Provider.
Security Measures:
Server-Side Credential Handling: Credentials are only handled within the Keycloak server environment.
No Exposure of Realm Information: Does not redirect the user or expose any realm-specific URLs or data to the client.
Brute-Force Protection: By not revealing user-realm associations, it prevents attackers from inferring relationships and targeting specific realms.
Key Components of the Authenticator Code:
Action Method:
Intercepts the authentication flow and retrieves the user based on the provided username.
Checks for federated identities associated with the user.
Password Validation:
If the user is linked to a federated Identity Provider, it performs programmatic authentication against that provider.
Otherwise, it proceeds with standard password validation.
Programmatic Authentication Against OIDC Identity Provider:
Constructs a token request to the Identity Provider's token endpoint.
Includes client credentials, username, and password in the request.
Parses the response to determine if authentication was successful.
Keycloak Configuration:
Authenticator Setup:
The custom Authenticator (
FolioEcsUsernamePasswordFormV2) must be deployed to the Keycloak server.Configure the authentication flow to include the custom Authenticator.
Identity Providers Configuration:
Identity Providers (other realms) must be set up in Keycloak with appropriate client credentials.
Ensure that the Identity Providers allow authentication using the Resource Owner Password Credentials Grant.
Pros
Enhanced Security
No Realm Information Exposure: Prevents intruders from discovering user-realm associations, as no redirection or realm hints are provided to the client.
Server-Side Credential Handling: Credentials are processed entirely on the server side, reducing the risk of interception.
Brute-Force Protection: Limits the ability of attackers to target specific realms or users.
Simplified User Experience
Unified Login Process: Users access a single login page without manual tenant selection or noticeable redirections.
Seamless Authentication: The authentication process is transparent to the user, regardless of their home realm.
No Client-Side Modifications Required