Single Sign-On (SSO) is an authentication method that allows users to access multiple applications with one set of credentials. Delphi's custom SSO implementation lets you streamlines user access to our website embed. This guide will walk you through setting up and using SSO with Delphi.
Important: The security of your SSO implementation relies heavily on keeping your private key confidential. Never share your private key or store it in unsecured locations.
This guide assumes you've already requested and been approved for SSO functionality. Contact support@delphi.ai to begin the process.
A pair of encryption keys, known as a key-pair, are used to make sure that your application and Delphi's API communicate securely by providing a way to verify that messages sent between one another are coming from a legitimate source.
Our first step is to generate those encryption keys. You have two options for generating your RSA key pair:
Open your terminal and run the following commands:
openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem
This will generate a private_key.pem
and a public_key.pem
file in your current directory.
- Navigate to the SSO settings in your Delphi dashboard.
- Click on the "Generate Key Pair" button.
- Follow the on-screen instructions to generate and download your key pair.
- In Delphi's Clone Studio, go to the settings page, and find Single Sign-on (SSO) in the menu bar.
- Paste your public key into the designated field and click Save.
To implement SSO in your application, you'll need to:
- Generate a JWT (JSON Web Token) signed with your private key.
- Send the JWT to the Delphi embed using the following JavaScript code:
Important Note: The only supported SSO field in the JWT payload is email. Any other fields in the JWT will be ignored, except for the standard expiry field. The only supported algorithim is RS256.
To authenticate the Delphi embedding in your app, you can run the following, providing the JWT you generated earlier:
document.getElementById("delphi-frame").contentWindow.postMessage(
{
type: "sso_login",
token: "your_generated_jwt_here",
},
"*"
);
A sample SSO application is available in sample-sso-app
. This demo showcases a basic implementation of the Delphi SSO flow and can serve as a reference for your own implementation.
Use Delphi's built-in JWT testing module to verify your token generation:
- In Delphi's Clone Studio, go to the settings page, and find Single Sign-on (SSO) in the menu bar.
- Navigate to the JWT testing section.
- Paste a sample JWT generated by your system.
- Click "Test JWT" to verify its validity and contents.
-
Protect Your Private Key: Store your private key in a secure location, such as a secret management system. Never expose it in client-side code or public repositories. The security of your users depends upon it, and Delphi reserves the right to revoke your SSO approval should these keys be mishandled.
-
Token Expiration: Set appropriate expiration times for your JWTs to limit the window of opportunity for potential replay attacks. The sample app sets a JWT expiry of 1 hour. The longer the expiration date, the less secure it is.
-
Robust Iframe Initialization: Many implementations face race conditions when initializing the Delphi embed and handling SSO. Here's what to watch out for:
Common Pitfalls A typical implementation often looks something like this:
// ❌ Problematic Implementation function initializeDelphiEmbed() { const iframe = document.getElementById("delphi-frame"); // This might fail if the scripts inside the iframe haven't loaded yet iframe.contentWindow.postMessage( { type: "sso_login", token: "your_jwt_token", }, "*" ); } // This might run too early window.onload = initializeDelphiEmbed;
This approach can fail because:
- The iframe might not be fully loaded when the message is sent
- The Delphi application inside the iframe might not be ready to receive messages
- There's no retry mechanism if the initialization fails
Recommended Implementation Instead, implement a more robust initialization with proper loading checks and retries. Here is some sample code to inspire your implementation.
class DelphiEmbed { constructor(iframeId, token) { console.log(`[DelphiEmbed] Initializing for "${iframeId}"`); this.iframeId = iframeId; this.token = token; this.maxRetries = 5; this.retryDelay = 500; this.loadTimeout = 10000; this.iframeRetries = 10; this.iframeRetryDelay = 200; } async findIframe() { let attempts = 0; while (attempts < this.iframeRetries) { const iframe = document.getElementById(this.iframeId); if (iframe) return iframe; await new Promise((resolve) => setTimeout(resolve, this.iframeRetryDelay) ); attempts++; } throw new Error( `Iframe "${this.iframeId}" not found after ${this.iframeRetries} attempts` ); } async initialize() { try { this.iframe = await this.findIframe(); console.log("[DelphiEmbed] Found iframe, starting initialization"); } catch (error) { console.error("[DelphiEmbed]", error); throw error; } return new Promise((resolve, reject) => { let retryCount = 0; const validateLocation = () => { try { const win = this.iframe.contentWindow; return ( win?.location?.href !== "about:blank" && win?.location?.protocol !== "about:" && win?.location?.origin !== "null" ); } catch (e) { return false; } }; const validateContent = () => { try { const win = this.iframe.contentWindow; if (!win?.document) return false; return !!( win.document.querySelector(".delphi-talk-main-content") || win.document.querySelector(".delphi-call-content") || win.document.querySelector(".delphi-profile-container") ); } catch (e) { return false; } }; const checkContent = () => { if (validateContent()) { clearTimeout(loadTimeout); this.iframe.contentWindow.postMessage( { type: "sso_login", token: this.token }, "*" ); console.log("[DelphiEmbed] SSO token sent successfully"); resolve(); return true; } return false; }; const retryCheck = () => { if (retryCount >= this.maxRetries) { clearTimeout(loadTimeout); reject( new Error("Failed to load iframe content after maximum retries") ); return; } retryCount++; setTimeout(() => { if (validateLocation()) { if (!checkContent()) retryCheck(); } else { retryCheck(); } }, this.retryDelay); }; const loadTimeout = setTimeout(() => { reject(new Error("Iframe load timeout")); }, this.loadTimeout); if (validateLocation()) { if (!checkContent()) retryCheck(); } else { retryCheck(); } this.iframe.addEventListener("load", () => { if (validateLocation()) checkContent(); }); }); } }
// Usage
console.log("[DelphiEmbed] Creating new instance");
const delphi = new DelphiEmbed("delphi-frame", "your_jwt_token");
delphi
.initialize()
.then(() => console.log("[DelphiEmbed] Initialized successfully"))
.catch((error) =>
console.error("[DelphiEmbed] Initialization failed:", error)
);
By following these guidelines and best practices, you'll ensure a secure and efficient SSO implementation.