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:
2025-06-14 15:26:26 +02:00
parent fa35fcd235
commit 0c9cd89667
8 changed files with 819 additions and 11 deletions

View File

@@ -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

View File

@@ -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