Create a 3rd Party Identity Provider Integration Framework for Genie-AI
Genie-AI 3rd Party Identity Integration Specification
This specification outlines the integration of the Genie-AI sign-on framework with third-party identity providers (IdPs) using Keycloak as an SSO adapter, supporting OAuth2 and OpenID Connect (OIDC) protocols. The integration ensures that the Genie-AI RAG application framework operates independently with its existing OAuth2-based authentication system while synchronizing with third-party IdPs, including social login providers (e.g., Google, Facebook) and institutional providers (e.g., government agencies). All relevant components, including the keycloakService.js and HTML usage example, are embedded within this document.
1. Overview
The Genie-AI RAG application framework requires a flexible authentication system that:
-
Operates Independently: Maintains its existing OAuth2-based authentication as implemented in
authService.jsanduserService.js. - Synchronizes with Third-Party IdPs: Integrates with external IdPs via Keycloak, supporting social and institutional providers.
- Ensures Seamless User Experience: Provides single sign-on (SSO), user profile synchronization, secure token management, and API key integration.
Keycloak serves as the SSO adapter, bridging Genie-AI with third-party IdPs, handling OAuth2/OIDC interactions, and providing a unified authentication interface.
2. Objectives
- Enable SSO with third-party IdPs using OAuth2 and OIDC.
- Synchronize user identities and profile data between Genie-AI and external IdPs.
- Maintain compatibility with the existing Genie-AI authentication system.
- Ensure security through secure token exchange, encryption, and validation.
- Support social (Google, Facebook) and institutional (government agencies) IdPs.
- Seamlessly integrate API key management for secure backend communication.
3. Technical Requirements
3.1 Dependencies
- Keycloak: Open-source identity and access management solution (version 22.x or later) for SSO and IdP integration.
-
Existing Services:
-
authService.js: Handles local authentication and token management. -
userService.js: Manages user accounts and profile data. -
userProfileService.js: Manages detailed user profiles. -
chatbotService.js: Handles chat interactions. -
chatHistoryService.js: Manages conversation history. -
apiKeyService.js: Manages API key generation and validation. -
httpService.js: Facilitates API communication.
-
-
New Service:
-
keycloakService.js: Manages Keycloak-specific operations (included below).
-
-
External Libraries:
-
keycloak-js: Keycloak JavaScript adapter for client-side integration. -
axios: For HTTP requests (assumed inhttpService.js). -
jsonwebtoken: For JWT validation in the backend. -
crypto: For secure random string generation and hashing. -
marked,DOMPurify,jsPDF: For chatbot functionality (fromChatBotComponent.vue).
-
-
Backend Requirements:
- Keycloak server with OAuth2/OIDC endpoints.
- Genie-AI backend APIs extended for Keycloak token validation and user synchronization.
3.2 Protocols
- OAuth2: For authorization and token exchange.
- OpenID Connect (OIDC): For authentication and user identity information via ID tokens.
- SAML 2.0: Optional support for institutional IdPs using SAML.
3.3 Browser Compatibility
- Compatible with modern browsers (Chrome, Firefox, Safari, Edge).
- Requires
keycloak-jsfor client-side OIDC integration. - Polyfills (e.g.,
webcomponents.js) for older browsers if using<genie-ai-chatbot>.
4. System Architecture
The architecture includes:
-
Genie-AI Client: The frontend (e.g.,
<genie-ai-chatbot>or web app) usingkeycloak-js. - Genie-AI Backend: Manages local user data, token validation, and profile synchronization.
- Keycloak Server: Handles SSO, IdP connections, and token issuance.
- Third-Party IdPs: Social providers (Google, Facebook) and institutional providers (government agencies).
4.1 Authentication Flow Diagram (OIDC with Keycloak)
The following diagram illustrates the OIDC authentication flow with a third-party IdP via Keycloak.
sequenceDiagram
participant User
participant Browser
participant GenieAIClient as Genie-AI Client
participant Keycloak
participant IdP as Third-Party IdP
participant GenieAIBackend as Genie-AI Backend
User->>Browser: Initiates login
Browser->>GenieAIClient: Loads <genie-ai-chatbot>
GenieAIClient->>Keycloak: Initialize Keycloak (keycloak-js)
Keycloak-->>GenieAIClient: Redirect to Keycloak login
Browser->>Keycloak: Access login page
Keycloak->>IdP: Redirect to IdP (OAuth2/OIDC)
User->>IdP: Authenticate (e.g., Google login)
IdP-->>Keycloak: ID token, access token
Keycloak-->>GenieAIClient: ID token, access token, refresh token
GenieAIClient->>GenieAIBackend: POST /auth/keycloak/sync (ID token)
GenieAIBackend->>GenieAIBackend: Validate ID token, sync user
GenieAIBackend-->>GenieAIClient: Genie-AI access token
GenieAIClient->>Browser: Store tokens, render chatbot
User->>Browser: Interact with chatbot
4.2 Token Refresh Flow
The following diagram shows the token refresh process using Keycloak.
sequenceDiagram
participant Browser
participant GenieAIClient as Genie-AI Client
participant Keycloak
participant GenieAIBackend as Genie-AI Backend
Browser->>GenieAIClient: User interaction
GenieAIClient->>Keycloak: Refresh token expired?
Keycloak-->>GenieAIClient: New ID token, access token
GenieAIClient->>GenieAIBackend: POST /auth/keycloak/refresh (refresh token)
GenieAIBackend-->>GenieAIClient: New Genie-AI access token
GenieAIClient->>Browser: Update tokens in localStorage
5. Integration Design
5.1 Keycloak Configuration
Keycloak is configured to support social and institutional IdPs.
5.1.1 Realm Setup
-
Realm: Create
genie-ai-realmin Keycloak. -
Clients:
-
Genie-AI Client: Public OIDC client (
client_id: genie-ai-client).- Redirect URI:
https://genie-ai-app.com/*(or third-party app URL). - Web Origins: Allow CORS for third-party domains.
- Redirect URI:
-
Backend Client: Confidential client (
client_id: genie-ai-backend).- Client Secret: Generated for secure backend communication.
-
Genie-AI Client: Public OIDC client (
-
Identity Providers:
-
Social IdPs:
- Google: Configure OAuth2 client ID and secret.
- Facebook: Configure OAuth2 app ID and secret.
-
Institutional IdPs:
- Government agencies: Configure OIDC or SAML 2.0 (e.g., using metadata URLs).
-
Social IdPs:
-
Mappers:
- Map IdP attributes (e.g.,
email,given_name,family_name) to Keycloak user attributes. - Custom mapper for third-party
user_idto sync with Genie-AIuserId.
- Map IdP attributes (e.g.,
-
User Federation:
- Enable federation to synchronize user data from IdPs.
- Store minimal data (email, user ID) in Keycloak, with additional data in Genie-AI backend.
5.2 Genie-AI Client Integration
The <genie-ai-chatbot> component is updated to support Keycloak authentication, extending the previous implementation.
5.2.1 Genie-AI Chatbot Component
The following is the updated GenieAIChatbot.js for Keycloak integration.
import { defineCustomElement } from 'vue';
import ChatBotComponent from './ChatBotComponent.vue';
import { createPinia } from 'pinia';
import { createI18n } from 'vue-i18n';
import Keycloak from 'keycloak-js';
import messages from './locales';
import httpService from './services/httpService';
import chatbotService from './services/chatbotService';
import chatHistoryService from './services/chatHistoryService';
import authService from './services/authService';
import userService from './services/userService';
import apiKeyService from './services/apiKeyService';
import keycloakService from './services/keycloakService';
const GenieAIChatbot = defineCustomElement({
props: {
apiKey: String,
userId: String,
authEndpoint: String,
chatEndpoint: String,
apiKeyEndpoint: String,
keycloakRealm: String,
keycloakUrl: String,
keycloakClientId: { type: String, default: 'genie-ai-client' },
theme: { type: String, default: 'light' },
locale: { type: String, default: 'en' },
welcomeMessage: String,
placeholder: String,
showQuickHelp: { type: Boolean, default: true },
allowPdfExport: { type: Boolean, default: true },
},
setup(props, { emit }) {
const pinia = createPinia();
const i18n = createI18n({
locale: props.locale,
fallbackLocale: 'en',
messages,
});
// Configure httpService
httpService.setBaseUrl(props.chatEndpoint);
httpService.setAuthBaseUrl(props.authEndpoint);
httpService.setApiKeyEndpoint(props.apiKeyEndpoint || props.authEndpoint);
keycloakService.setConfig({ keycloakUrl: props.keycloakUrl, realm: props.keycloakRealm });
return {
...ChatBotComponent.setup({
props,
emit: (eventName, payload) => {
const event = new CustomEvent(`genie-ai-${eventName}`, { detail: payload });
this.dispatchEvent(event);
},
}),
pinia,
i18n,
};
},
created() {
this.initialize();
},
methods: {
async initialize() {
try {
// Initialize Keycloak
this.keycloak = new Keycloak({
url: this.keycloakUrl,
realm: this.keycloakRealm,
clientId: this.keycloakClientId,
});
await this.keycloak.init({ onLoad: 'check-sso' });
// Initialize API key
let apiKey = this.apiKey || apiKeyService.getApiKey();
if (!apiKey || !(await apiKeyService.validateApiKey(apiKey))) {
apiKey = await apiKeyService.generateApiKey(this.userId);
this.$emit('api-key', { apiKey, userId: this.userId });
}
httpService.setApiKey(apiKey);
// Authenticate and sync user
await this.initializeUser();
this.$emit('ready', { status: 'ready' });
} catch (error) {
console.error('Initialization error:', error);
this.$emit('error', { error: 'Initialization failed', details: error });
}
},
async initializeUser() {
try {
if (!this.keycloak.authenticated) {
await this.keycloak.login();
}
const idToken = this.keycloak.idToken;
const userProfile = this.keycloak.idTokenParsed;
// Sync user with Genie-AI backend
const response = await keycloakService.syncUser({
idToken,
userId: this.userId,
email: userProfile.email,
fullName: userProfile.name,
thirdPartyId: userProfile.sub,
});
userService.setUserData({
accessToken: response.accessToken,
refreshToken: this.keycloak.refreshToken,
userId: this.userId,
_key: response._key,
});
this.$emit('auth', { status: 'authenticated', userId: this.userId });
// Start session
const session = await chatbotService.startSession(this.userId);
this.currentSessionId = session._key;
} catch (error) {
console.error('User initialization error:', error);
this.$emit('error', { error: 'Authentication failed', details: error });
}
},
async sendMessage() {
try {
if (!this.keycloak.authenticated) {
await this.keycloak.login();
}
const result = await ChatBotComponent.methods.sendMessage.call(this);
this.$emit('message', {
sender: result.sender,
content: result.content,
timestamp: result.timestamp,
});
} catch (error) {
this.$emit('error', { error: 'Message sending failed', details: error });
}
},
async exportChatToPDF() {
if (!this.allowPdfExport) return;
try {
await ChatBotComponent.methods.exportChatToPDF.call(this);
this.$emit('export', { filename: this.exportDialog.filename });
} catch (error) {
this.$emit('error', { error: 'PDF export failed', details: error });
}
},
},
});
customElements.define('genie-ai-chatbot', GenieAIChatbot);
5.3 Backend Integration
The Genie-AI backend is extended with a new keycloakService.js to handle Keycloak-specific operations.
5.3.1 Keycloak Service
The following is the keycloakService.js implementation.
import httpService from './httpService';
import jwt from 'jsonwebtoken';
class KeycloakService {
constructor() {
this.keycloakUrl = '';
this.realm = '';
this.clientId = 'genie-ai-backend';
}
setConfig({ keycloakUrl, realm }) {
this.keycloakUrl = keycloakUrl;
this.realm = realm;
}
/**
* Validate Keycloak ID token
* @param {String} idToken - Keycloak ID token
* @returns {Promise} Decoded user data
*/
async validateIdToken(idToken) {
try {
const jwksUri = `${this.keycloakUrl}/realms/${this.realm}/protocol/openid-connect/certs`;
const jwksResponse = await httpService.get(jwksUri);
const decoded = jwt.verify(idToken, jwksResponse.data.keys[0], {
algorithms: ['RS256'],
});
return decoded;
} catch (error) {
console.error('Error validating ID token:', error);
throw error;
}
}
/**
* Sync user from Keycloak to Genie-AI
* @param {Object} data - User data from Keycloak
* @returns {Promise} Genie-AI user data
*/
async syncUser({ idToken, userId, email, fullName, thirdPartyId }) {
try {
const decoded = await this.validateIdToken(idToken);
let user = await userService.getCurrentUser();
if (!user) {
user = await userService.register({
loginName: `user_${userId}`,
email: email || `user_${userId}@genie-ai.com`,
password: crypto.randomUUID(),
fullName,
});
}
// Update user profile with third-party data
await userProfileService.updateProfile(user._key, {
thirdPartyId,
email: decoded.email,
fullName: decoded.name,
});
// Issue Genie-AI token
const genieAIToken = await this.issueGenieAIToken(user._key);
return { _key: user._key, accessToken: genieAIToken };
} catch (error) {
console.error('Error syncing user:', error);
throw error;
}
}
/**
* Issue Genie-AI access token
* @param {String} userId - Genie-AI user ID
* @returns {Promise} Genie-AI access token
*/
async issueGenieAIToken(userId) {
try {
const response = await httpService.post('/auth/token', { userId });
return response.data.accessToken;
} catch (error) {
console.error('Error issuing Genie-AI token:', error);
throw error;
}
}
/**
* Refresh Genie-AI token using Keycloak refresh token
* @param {String} refreshToken - Keycloak refresh token
* @returns {Promise} New Genie-AI access token
*/
async refreshGenieAIToken(refreshToken) {
try {
const response = await httpService.post('/auth/keycloak/refresh', { refreshToken });
return response.data.accessToken;
} catch (error) {
console.error('Error refreshing Genie-AI token:', error);
throw error;
}
}
}
export default new KeycloakService();
5.4 HTTP Service Update
The httpService.js is updated to support Keycloak and API key authentication.
import axios from 'axios';
class HttpService {
constructor() {
this.axiosInstance = axios.create();
this.baseUrl = '';
this.authBaseUrl = '';
this.apiKeyEndpoint = '';
this.apiKey = '';
}
setBaseUrl(url) {
this.baseUrl = url;
}
setAuthBaseUrl(url) {
this.authBaseUrl = url;
}
setApiKeyEndpoint(url) {
this.apiKeyEndpoint = url;
}
setApiKey(apiKey) {
this.apiKey = apiKey;
}
async request(method, url, data = null, config = {}) {
try {
const headers = {
...config.headers,
'X-API-Key': this.apiKey,
};
const response = await this.axiosInstance({
method,
url: url.startsWith('/auth') ? `${this.authBaseUrl}${url}` : url.startsWith('/apikeys') ? `${this.apiKeyEndpoint}${url}` : `${this.baseUrl}${url}`,
data,
headers,
...config,
});
return response;
} catch (error) {
console.error(`HTTP ${method} request failed:`, error);
throw error;
}
}
get(url, config = {}) {
return this.request('get', url, null, config);
}
post(url, data, config = {}) {
return this.request('post', url, data, config);
}
put(url, data, config = {}) {
return this.request('put', url, data, config);
}
patch(url, data, config = {}) {
return this.request('patch', url, data, config);
}
delete(url, config = {}) {
return this.request('delete', url, null, config);
}
}
export default new HttpService();
5.5 API Key Service
The apiKeyService.js ensures secure API key management.
import httpService from './httpService';
import crypto from 'crypto';
class ApiKeyService {
constructor() {
this.apiKeyKey = 'genie_ai_api_key';
this.apiKeyEndpoint = 'apikeys';
}
/**
* Generate a new API key for a user
* @param {String} userId - User ID
* @returns {Promise} API key data
*/
async generateApiKey(userId) {
try {
if (!userId) {
throw new Error('User ID is required');
}
const response = await httpService.post(`${this.apiKeyEndpoint}/generate`, {
userId,
scope: ['chat', 'auth'],
expiresIn: '365d',
});
const apiKey = response.data.apiKey;
this.setApiKey(apiKey);
return apiKey;
} catch (error) {
console.error('Error generating API key:', error);
throw error;
}
}
/**
* Validate an API key
* @param {String} apiKey - API key to validate
* @returns {Promise} Validation result
*/
async validateApiKey(apiKey) {
try {
const response = await httpService.post(`${this.apiKeyEndpoint}/validate`, { apiKey });
return response.data.valid;
} catch (error) {
console.error('Error validating API key:', error);
return false;
}
}
/**
* Revoke an API key
* @param {String} apiKey - API key to revoke
* @param {String} userId - User ID for permission check
* @returns {Promise} Revocation result
*/
async revokeApiKey(apiKey, userId) {
try {
const response = await httpService.delete(`${this.apiKeyEndpoint}/${apiKey}`, {
params: { userId }
});
this.clearApiKey();
return response.data;
} catch (error) {
console.error('Error revoking API key:', error);
throw error;
}
}
/**
* Get stored API key
* @returns {String|null} Stored API key or null
*/
getApiKey() {
try {
return localStorage.getItem(this.apiKeyKey);
} catch (e) {
console.error('Error accessing API key:', e);
return null;
}
}
/**
* Set API key in local storage
* @param {String} apiKey - API key to store
*/
setApiKey(apiKey) {
localStorage.setItem(this.apiKeyKey, apiKey);
}
/**
* Clear API key from local storage
*/
clearApiKey() {
localStorage.removeItem(this.apiKeyKey);
}
}
export default new ApiKeyService();
5.6 Independent Authentication
The existing OAuth2-based authentication in authService.js and userService.js remains functional:
- Users can log in directly using
userService.login()with username/password. - Tokens are stored in
localStorageand refreshed viaauthService.refreshToken(). - The backend validates local tokens independently for non-SSO users.
5.7 User Profile Synchronization
-
Keycloak to Genie-AI: Sync attributes (e.g., email, name) from Keycloak to
userProfileServiceduring login. - Genie-AI to Keycloak: Optional updates to Keycloak via Admin API (requires backend implementation).
- Conflict Resolution: Prioritize Keycloak attributes for SSO logins, allow manual updates in Genie-AI.
6. Security Considerations
-
Token Security:
- Use HTTPS for all communications.
- Store tokens in
localStoragewith short-lived access tokens (15 minutes) and refresh tokens.
-
Keycloak Security:
- Validate ID tokens using Keycloak's JWKS endpoint.
- Use PKCE for public clients to prevent code interception.
- CORS: Configure Keycloak and Genie-AI backend for CORS from third-party domains.
- Rate Limiting: Apply to authentication endpoints to prevent abuse.
- Data Privacy: Minimize stored data, comply with GDPR/CCPA.
- API Keys: Scoped to user and endpoints, stored securely, and validated for each request.
7. Usage Example
7.1 Embedding in HTML
The following HTML demonstrates how to embed the <genie-ai-chatbot> component with Keycloak integration.
<!DOCTYPE html>
<html>
<head>
<title>Third-Party App with Genie-AI Chatbot</title>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/keycloak-js@22/dist/keycloak.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/genie-ai-chatbot/dist/GenieAIChatbot.js"></script>
</head>
<body>
<genie-ai-chatbot
user-id="user123"
auth-endpoint="https://api.genie-ai.com/auth"
chat-endpoint="https://api.genie-ai.com/chat"
api-key-endpoint="https://api.genie-ai.com/apikeys"
keycloak-realm="genie-ai-realm"
keycloak-url="https://keycloak.genie-ai.com/auth"
keycloak-client-id="genie-ai-client"
theme="dark"
locale="en"
welcome-message="Welcome to Genie-AI Support!"
placeholder="Type your message here..."
show-quick-help="true"
allow-pdf-export="true"
></genie-ai-chatbot>
<script>
const chatbot = document.querySelector('genie-ai-chatbot');
chatbot.addEventListener('genie-ai-ready', () => console.log('Chatbot ready'));
chatbot.addEventListener('genie-ai-auth', (e) => console.log('Auth:', e.detail));
chatbot.addEventListener('genie-ai-message', (e) => console.log('Message:', e.detail));
chatbot.addEventListener('genie-ai-error', (e) => console.error('Error:', e.detail));
chatbot.addEventListener('genie-ai-api-key', (e) => console.log('API Key:', e.detail));
chatbot.addEventListener('genie-ai-export', (e) => console.log('Exported:', e.detail));
</script>
</body>
</html>
7.2 Backend Configuration
-
Keycloak:
- Deploy Keycloak with
genie-ai-realm. - Configure IdPs (Google, Facebook, government agencies).
- Set up clients (
genie-ai-client,genie-ai-backend).
- Deploy Keycloak with
-
Genie-AI Backend:
- Support
/auth/keycloak/syncand/auth/keycloak/refreshendpoints. - Validate Keycloak tokens using JWKS.
- Maintain user mapping between third-party
user-idand Genie-AI_key. - Support
/apikeysendpoints for API key management.
- Support
8. Customization Options
-
IdP Selection: Add a
preferred-idpprop to prioritize specific IdPs. - Branding: Customize Keycloak login pages for third-party branding.
- Profile Mapping: Configure attribute mappings for specific IdPs.
- API Key Notification: Display notifications for API key generation.
9. Limitations and Considerations
-
Dependency Overhead: Keycloak and
keycloak-jsincrease bundle size. - Keycloak Availability: Requires a reliable Keycloak server.
- Complex IdPs: Institutional IdPs may require custom configurations.
- Performance: SSO redirects may increase latency.
- Bundle Size: Vue.js and dependencies require optimization (e.g., CDN).
10. Future Enhancements
- SAML Support: Enhance for SAML-based IdPs.
- Custom Login UI: Allow third-party custom login interfaces.
- Offline Authentication: Cache tokens for offline access.
- Advanced Sync: Bidirectional profile synchronization.
- Analytics: Emit usage analytics events.
11. Deployment
- Keycloak: Deploy on a secure server with HTTPS.
- Genie-AI Client: Package as a CDN-hosted bundle.
- Documentation: Provide guides for Keycloak setup, IdP integration, and client configuration.
This specification ensures seamless SSO integration with third-party IdPs while preserving Genie-AI's independent authentication and supporting API key management.