Implement Official Keycloak JS Adapter with Proxy-Aware Configuration
MAJOR ENHANCEMENT: Complete Keycloak integration with proper HTTPS/proxy handling ## Core Improvements: ### 1. Enhanced Configuration (nuxt.config.ts) - Added proxy trust configuration for nginx environments - Configured baseUrl for production HTTPS enforcement - Added debug mode configuration for development ### 2. Proxy-Aware Keycloak Composable (composables/useKeycloak.ts) - Intelligent base URL detection (production vs development) - Force HTTPS redirect URIs in production environments - Enhanced debugging and logging capabilities - Proper PKCE implementation for security - Automatic token refresh mechanism ### 3. Dual Authentication System - Updated middleware to support both Directus and Keycloak - Enhanced useUnifiedAuth for seamless auth source switching - Maintains backward compatibility with existing Directus users ### 4. OAuth Flow Implementation - Created proper callback handler (pages/auth/callback.vue) - Comprehensive error handling and user feedback - Automatic redirect to dashboard on success ### 5. Enhanced Login Experience (pages/login.vue) - Restored SSO login button with proper error handling - Maintained existing Directus login form - Clear separation between auth methods with visual divider ### 6. Comprehensive Testing Suite (pages/dashboard/keycloak-test.vue) - Real-time configuration display - Authentication status monitoring - Interactive testing tools - Detailed debug logging system ## Technical Solutions: **Proxy Detection**: Automatically detects nginx proxy and uses correct HTTPS URLs **HTTPS Enforcement**: Forces secure redirect URIs in production **Error Handling**: Comprehensive error catching with user-friendly messages **Debug Capabilities**: Enhanced logging for troubleshooting **Security**: Implements PKCE and secure token handling ## Infrastructure Compatibility: - Works with nginx reverse proxy setups - Compatible with Docker container networking - Handles SSL termination at proxy level - Supports both development and production environments This implementation specifically addresses the HTTP/HTTPS redirect URI mismatch that was causing 'unauthorized_client' errors in the proxy environment.
This commit is contained in:
@@ -8,10 +8,44 @@ export const useKeycloak = () => {
|
||||
const token = ref<string | null>(null)
|
||||
const isInitialized = ref(false)
|
||||
|
||||
// Get the correct base URL considering proxy setup
|
||||
const getBaseUrl = () => {
|
||||
if (process.server) {
|
||||
// In production, always use the configured HTTPS base URL
|
||||
return config.public.baseUrl
|
||||
}
|
||||
|
||||
// Client-side: detect if we're behind a proxy
|
||||
const currentOrigin = window.location.origin
|
||||
const isProduction = !currentOrigin.includes('localhost') && !currentOrigin.includes('127.0.0.1')
|
||||
|
||||
if (isProduction) {
|
||||
// Force HTTPS in production environments
|
||||
return config.public.baseUrl
|
||||
}
|
||||
|
||||
// Development: use current origin
|
||||
return currentOrigin
|
||||
}
|
||||
|
||||
const logDebug = (message: string, data?: any) => {
|
||||
if (config.public.keycloakDebug) {
|
||||
console.log(`[KEYCLOAK] ${message}`, data)
|
||||
}
|
||||
}
|
||||
|
||||
const initKeycloak = async () => {
|
||||
if (process.server) return false
|
||||
|
||||
try {
|
||||
const baseUrl = getBaseUrl()
|
||||
logDebug('Initializing Keycloak', {
|
||||
baseUrl,
|
||||
keycloakUrl: config.public.keycloak.url,
|
||||
realm: config.public.keycloak.realm,
|
||||
clientId: config.public.keycloak.clientId
|
||||
})
|
||||
|
||||
// Dynamically import keycloak-js
|
||||
const KeycloakModule = await import('keycloak-js')
|
||||
const KeycloakConstructor = KeycloakModule.default
|
||||
@@ -24,8 +58,16 @@ export const useKeycloak = () => {
|
||||
|
||||
const authenticated = await keycloak.value.init({
|
||||
onLoad: 'check-sso',
|
||||
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
|
||||
// Use proper HTTPS redirect URI
|
||||
redirectUri: `${baseUrl}/auth/callback`,
|
||||
silentCheckSsoRedirectUri: `${baseUrl}/silent-check-sso.html`,
|
||||
checkLoginIframe: false, // Disable iframe checks for better compatibility
|
||||
pkceMethod: 'S256', // Use PKCE for better security
|
||||
})
|
||||
|
||||
logDebug('Keycloak initialization result', {
|
||||
authenticated,
|
||||
redirectUri: `${baseUrl}/auth/callback`
|
||||
})
|
||||
|
||||
isAuthenticated.value = authenticated
|
||||
|
||||
@@ -8,19 +8,42 @@ export interface UnifiedUser {
|
||||
}
|
||||
|
||||
export const useUnifiedAuth = () => {
|
||||
// Get auth system (Directus only for now)
|
||||
// Get both auth systems
|
||||
const directusAuth = useDirectusAuth();
|
||||
const directusUser = useDirectusUser();
|
||||
const keycloak = useKeycloak();
|
||||
|
||||
// Create unified user object (Directus only)
|
||||
// Create unified user object
|
||||
const user = computed<UnifiedUser | null>(() => {
|
||||
// Only use Directus user for now
|
||||
// Check Keycloak user first
|
||||
if (keycloak.user?.value) {
|
||||
const keycloakUser = keycloak.user.value;
|
||||
// Construct name from available fields
|
||||
let name = keycloakUser.fullName;
|
||||
if (!name && (keycloakUser.firstName || keycloakUser.lastName)) {
|
||||
name = `${keycloakUser.firstName || ''} ${keycloakUser.lastName || ''}`.trim();
|
||||
}
|
||||
if (!name) {
|
||||
name = keycloakUser.username || keycloakUser.email;
|
||||
}
|
||||
|
||||
return {
|
||||
id: keycloakUser.id,
|
||||
email: keycloakUser.email,
|
||||
name: name,
|
||||
tier: 'basic', // Could be enhanced with Keycloak attributes
|
||||
authSource: 'keycloak',
|
||||
raw: keycloakUser
|
||||
};
|
||||
}
|
||||
|
||||
// Fall back to Directus user
|
||||
if (directusUser.value && directusUser.value.email) {
|
||||
return {
|
||||
id: directusUser.value.id,
|
||||
email: directusUser.value.email,
|
||||
name: `${directusUser.value.first_name || ''} ${directusUser.value.last_name || ''}`.trim() || directusUser.value.email,
|
||||
tier: directusUser.value.tier || 'basic', // If Directus has tier field
|
||||
tier: directusUser.value.tier || 'basic',
|
||||
authSource: 'directus',
|
||||
raw: directusUser.value
|
||||
};
|
||||
@@ -29,11 +52,16 @@ export const useUnifiedAuth = () => {
|
||||
return null;
|
||||
});
|
||||
|
||||
// Unified logout function (Directus only)
|
||||
// Unified logout function
|
||||
const logout = async () => {
|
||||
// Directus logout
|
||||
await directusAuth.logout();
|
||||
await navigateTo('/login');
|
||||
if (user.value?.authSource === 'keycloak') {
|
||||
// Keycloak logout
|
||||
await keycloak.logout();
|
||||
} else if (user.value?.authSource === 'directus') {
|
||||
// Directus logout
|
||||
await directusAuth.logout();
|
||||
await navigateTo('/login');
|
||||
}
|
||||
};
|
||||
|
||||
// Check if user is authenticated
|
||||
|
||||
Reference in New Issue
Block a user