MAJOR: Replace nuxt-openid-connect with official Keycloak JS adapter
- Remove problematic nuxt-openid-connect module that was causing OAuth issues - Install and implement official keycloak-js adapter for better reliability - Create new useKeycloak composable with proper token management - Update useUnifiedAuth to work with new Keycloak implementation - Fix authentication middleware to support both auth methods - Update login page to use new Keycloak login function - Clean up configuration and remove deprecated OIDC settings - This should resolve all the HTTP/HTTPS redirect and token exchange issues
This commit is contained in:
129
composables/useKeycloak.ts
Normal file
129
composables/useKeycloak.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import type Keycloak from 'keycloak-js'
|
||||
|
||||
export const useKeycloak = () => {
|
||||
const config = useRuntimeConfig()
|
||||
const keycloak = ref<Keycloak | null>(null)
|
||||
const isAuthenticated = ref(false)
|
||||
const user = ref<any>(null)
|
||||
const token = ref<string | null>(null)
|
||||
const isInitialized = ref(false)
|
||||
|
||||
const initKeycloak = async () => {
|
||||
if (process.server) return
|
||||
|
||||
try {
|
||||
// Dynamically import keycloak-js
|
||||
const KeycloakModule = await import('keycloak-js')
|
||||
const KeycloakConstructor = KeycloakModule.default
|
||||
|
||||
keycloak.value = new KeycloakConstructor({
|
||||
url: config.public.keycloak.url,
|
||||
realm: config.public.keycloak.realm,
|
||||
clientId: config.public.keycloak.clientId,
|
||||
})
|
||||
|
||||
const authenticated = await keycloak.value.init({
|
||||
onLoad: 'check-sso',
|
||||
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
|
||||
checkLoginIframe: false, // Disable iframe checks for better compatibility
|
||||
})
|
||||
|
||||
isAuthenticated.value = authenticated
|
||||
isInitialized.value = true
|
||||
|
||||
if (authenticated && keycloak.value.token) {
|
||||
token.value = keycloak.value.token || null
|
||||
user.value = {
|
||||
id: keycloak.value.subject,
|
||||
username: keycloak.value.tokenParsed?.preferred_username,
|
||||
email: keycloak.value.tokenParsed?.email,
|
||||
firstName: keycloak.value.tokenParsed?.given_name,
|
||||
lastName: keycloak.value.tokenParsed?.family_name,
|
||||
fullName: keycloak.value.tokenParsed?.name,
|
||||
roles: keycloak.value.tokenParsed?.realm_access?.roles || [],
|
||||
}
|
||||
|
||||
// Set up token refresh
|
||||
keycloak.value.onTokenExpired = () => {
|
||||
keycloak.value?.updateToken(30).then((refreshed) => {
|
||||
if (refreshed) {
|
||||
token.value = keycloak.value?.token || null
|
||||
console.log('Token refreshed')
|
||||
} else {
|
||||
console.log('Token still valid')
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('Failed to refresh token')
|
||||
logout()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return authenticated
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Keycloak:', error)
|
||||
isInitialized.value = true
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const login = async () => {
|
||||
if (!keycloak.value) {
|
||||
await initKeycloak()
|
||||
}
|
||||
|
||||
if (keycloak.value) {
|
||||
try {
|
||||
await keycloak.value.login({
|
||||
redirectUri: window.location.origin + '/dashboard'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
if (keycloak.value) {
|
||||
try {
|
||||
await keycloak.value.logout({
|
||||
redirectUri: window.location.origin
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Logout failed:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear local state
|
||||
isAuthenticated.value = false
|
||||
user.value = null
|
||||
token.value = null
|
||||
}
|
||||
|
||||
const getToken = () => {
|
||||
return token.value
|
||||
}
|
||||
|
||||
const hasRole = (role: string) => {
|
||||
return user.value?.roles?.includes(role) || false
|
||||
}
|
||||
|
||||
const hasAnyRole = (roles: string[]) => {
|
||||
return roles.some(role => hasRole(role))
|
||||
}
|
||||
|
||||
return {
|
||||
keycloak: readonly(keycloak),
|
||||
isAuthenticated: readonly(isAuthenticated),
|
||||
user: readonly(user),
|
||||
token: readonly(token),
|
||||
isInitialized: readonly(isInitialized),
|
||||
initKeycloak,
|
||||
login,
|
||||
logout,
|
||||
getToken,
|
||||
hasRole,
|
||||
hasAnyRole,
|
||||
}
|
||||
}
|
||||
@@ -11,13 +11,13 @@ export const useUnifiedAuth = () => {
|
||||
// Get both auth systems
|
||||
const directusAuth = useDirectusAuth();
|
||||
const directusUser = useDirectusUser();
|
||||
const oidc = useOidc();
|
||||
const keycloak = useKeycloak();
|
||||
|
||||
// Create unified user object
|
||||
const user = computed<UnifiedUser | null>(() => {
|
||||
// Check Keycloak user first
|
||||
if (oidc.user?.value) {
|
||||
const keycloakUser = oidc.user.value;
|
||||
if (keycloak.user.value) {
|
||||
const keycloakUser = keycloak.user.value;
|
||||
// Construct name from firstName and lastName if available
|
||||
let name = keycloakUser.name;
|
||||
if (!name && (keycloakUser.given_name || keycloakUser.family_name)) {
|
||||
@@ -58,7 +58,7 @@ export const useUnifiedAuth = () => {
|
||||
const logout = async () => {
|
||||
if (user.value?.authSource === 'keycloak') {
|
||||
// Keycloak logout
|
||||
await oidc.logout();
|
||||
await keycloak.logout();
|
||||
} else if (user.value?.authSource === 'directus') {
|
||||
// Directus logout
|
||||
await directusAuth.logout();
|
||||
|
||||
Reference in New Issue
Block a user