Plain JavaScript Integration
Ce contenu n’est pas encore disponible dans votre langue.
This guide covers how to integrate Skycloak authentication into plain JavaScript applications without any framework dependencies.
Prerequisites
Section titled “Prerequisites”- Modern web browser with ES6+ support
- Skycloak cluster with configured realm and client
- Basic web server to serve your application
- Understanding of JavaScript promises and async/await
Quick Start
Section titled “Quick Start”1. Include Keycloak Adapter
Section titled “1. Include Keycloak Adapter”Download and include the Keycloak JavaScript adapter:
<!-- Option 1: CDN -->
<script src="https://cdn.jsdelivr.net/npm/keycloak-js@latest/dist/keycloak.min.js"></script>
<!-- Option 2: Local file -->
<script src="js/keycloak.min.js"></script>
2. Basic HTML Structure
Section titled “2. Basic HTML Structure”<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Skycloak JavaScript App</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="app">
<header>
<h1>My Application</h1>
<div id="auth-status">
<button id="login-btn" style="display: none;">Login</button>
<div id="user-info" style="display: none;">
<span id="username"></span>
<button id="logout-btn">Logout</button>
</div>
</div>
</header>
<main id="content">
<div id="loading">Initializing authentication...</div>
<div id="public-content" style="display: none;">
<h2>Public Content</h2>
<p>This content is visible to everyone.</p>
</div>
<div id="protected-content" style="display: none;">
<h2>Protected Content</h2>
<p>This content requires authentication.</p>
</div>
</main>
</div>
<script src="js/keycloak.min.js"></script>
<script src="js/app.js"></script>
</body>
</html>
3. Initialize Keycloak
Section titled “3. Initialize Keycloak”Create js/app.js:
// Keycloak configuration
const keycloakConfig = {
url: 'https://your-cluster-id.app.skycloak.io',
realm: 'your-realm',
clientId: 'your-javascript-app',
};
// Initialize Keycloak instance
const keycloak = new Keycloak(keycloakConfig);
// Authentication state
let authenticated = false;
// Initialize Keycloak
async function initKeycloak() {
try {
authenticated = await keycloak.init({
onLoad: 'check-sso',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
checkLoginIframe: false,
pkceMethod: 'S256', // Enable PKCE for public clients
});
if (authenticated) {
console.log('User is authenticated');
showAuthenticatedUI();
setupTokenRefresh();
} else {
console.log('User is not authenticated');
showPublicUI();
}
} catch (error) {
console.error('Failed to initialize Keycloak', error);
showError('Authentication system unavailable');
}
}
// Show authenticated UI
function showAuthenticatedUI() {
document.getElementById('loading').style.display = 'none';
document.getElementById('login-btn').style.display = 'none';
document.getElementById('user-info').style.display = 'block';
document.getElementById('username').textContent = keycloak.tokenParsed.preferred_username;
document.getElementById('public-content').style.display = 'block';
document.getElementById('protected-content').style.display = 'block';
}
// Show public UI
function showPublicUI() {
document.getElementById('loading').style.display = 'none';
document.getElementById('login-btn').style.display = 'block';
document.getElementById('user-info').style.display = 'none';
document.getElementById('public-content').style.display = 'block';
document.getElementById('protected-content').style.display = 'none';
}
// Show error message
function showError(message) {
document.getElementById('loading').textContent = message;
}
// Setup event listeners
document.addEventListener('DOMContentLoaded', () => {
// Login button
document.getElementById('login-btn').addEventListener('click', () => {
keycloak.login();
});
// Logout button
document.getElementById('logout-btn').addEventListener('click', () => {
keycloak.logout();
});
// Initialize Keycloak
initKeycloak();
});
// Token refresh
function setupTokenRefresh() {
setInterval(() => {
keycloak
.updateToken(30)
.then((refreshed) => {
if (refreshed) {
console.log('Token refreshed');
}
})
.catch(() => {
console.error('Failed to refresh token');
keycloak.login();
});
}, 60000); // Check every minute
}
4. Create Silent Check SSO Page
Section titled “4. Create Silent Check SSO Page”Create silent-check-sso.html:
<!DOCTYPE html>
<html>
<head>
<title>Silent SSO Check</title>
</head>
<body>
<script>
parent.postMessage(location.href, location.origin);
</script>
</body>
</html>
Advanced Implementation
Section titled “Advanced Implementation”Complete Authentication Manager
Section titled “Complete Authentication Manager”// js/auth-manager.js
class AuthManager {
constructor(config) {
this.keycloak = new Keycloak(config);
this.authenticated = false;
this.userInfo = null;
this.callbacks = {
onAuthenticated: [],
onLogout: [],
onTokenExpired: [],
onTokenRefreshed: [],
onAuthError: [],
};
}
// Initialize authentication
async init(options = {}) {
const defaultOptions = {
onLoad: 'check-sso',
checkLoginIframe: false,
pkceMethod: 'S256',
silentCheckSsoRedirectUri: window.location.origin + '/silent-check-sso.html',
};
try {
this.authenticated = await this.keycloak.init({ ...defaultOptions, ...options });
if (this.authenticated) {
await this.loadUserInfo();
this.setupTokenManagement();
this.trigger('onAuthenticated', this.userInfo);
}
this.setupEventHandlers();
return this.authenticated;
} catch (error) {
this.trigger('onAuthError', error);
throw error;
}
}
// Load user information
async loadUserInfo() {
try {
this.userInfo = await this.keycloak.loadUserInfo();
this.userInfo.roles = this.getRoles();
this.userInfo.permissions = this.getPermissions();
return this.userInfo;
} catch (error) {
console.error('Failed to load user info:', error);
throw error;
}
}
// Get user roles
getRoles() {
const token = this.keycloak.tokenParsed;
if (!token) return { realm: [], client: {} };
return {
realm: token.realm_access?.roles || [],
client: token.resource_access || {},
};
}
// Get user permissions
getPermissions() {
// Extract permissions from token if available
const token = this.keycloak.tokenParsed;
return token?.permissions || [];
}
// Check if user has a specific role
hasRole(role, clientId = null) {
const roles = this.getRoles();
if (clientId) {
const clientRoles = roles.client[clientId]?.roles || [];
return clientRoles.includes(role);
}
return roles.realm.includes(role);
}
// Check if user has any of the specified roles
hasAnyRole(...roles) {
const userRoles = this.getRoles().realm;
return roles.some((role) => userRoles.includes(role));
}
// Check if user has all specified roles
hasAllRoles(...roles) {
const userRoles = this.getRoles().realm;
return roles.every((role) => userRoles.includes(role));
}
// Login
async login(options = {}) {
return this.keycloak.login(options);
}
// Logout
async logout(options = {}) {
return this.keycloak.logout(options);
}
// Register
async register(options = {}) {
return this.keycloak.register(options);
}
// Get access token
async getToken() {
if (this.keycloak.isTokenExpired(5)) {
await this.refreshToken();
}
return this.keycloak.token;
}
// Refresh token
async refreshToken() {
try {
const refreshed = await this.keycloak.updateToken(5);
if (refreshed) {
this.trigger('onTokenRefreshed', this.keycloak.token);
}
return refreshed;
} catch (error) {
this.trigger('onTokenExpired');
throw error;
}
}
// Setup token management
setupTokenManagement() {
// Auto-refresh tokens
setInterval(async () => {
try {
await this.refreshToken();
} catch (error) {
console.error('Token refresh failed:', error);
}
}, 30000); // Every 30 seconds
// Refresh on token expiry
this.keycloak.onTokenExpired = () => {
this.trigger('onTokenExpired');
this.refreshToken().catch(() => {
this.login();
});
};
}
// Setup event handlers
setupEventHandlers() {
this.keycloak.onAuthSuccess = () => {
console.log('Auth success');
};
this.keycloak.onAuthError = (error) => {
console.error('Auth error:', error);
this.trigger('onAuthError', error);
};
this.keycloak.onAuthLogout = () => {
this.authenticated = false;
this.userInfo = null;
this.trigger('onLogout');
};
}
// Event handling
on(event, callback) {
if (this.callbacks[event]) {
this.callbacks[event].push(callback);
}
}
trigger(event, data) {
if (this.callbacks[event]) {
this.callbacks[event].forEach((callback) => callback(data));
}
}
// Make authenticated API call
async fetch(url, options = {}) {
const token = await this.getToken();
return fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
}
}
// Export for use
window.AuthManager = AuthManager;
UI Components
Section titled “UI Components”// js/ui-components.js
class UIComponents {
constructor(authManager) {
this.auth = authManager;
}
// Create login button
createLoginButton(options = {}) {
const button = document.createElement('button');
button.textContent = options.text || 'Login';
button.className = options.className || 'btn btn-primary';
button.onclick = () => this.auth.login(options.loginOptions);
return button;
}
// Create logout button
createLogoutButton(options = {}) {
const button = document.createElement('button');
button.textContent = options.text || 'Logout';
button.className = options.className || 'btn btn-secondary';
button.onclick = () => this.auth.logout(options.logoutOptions);
return button;
}
// Create user profile widget
createUserProfile() {
const container = document.createElement('div');
container.className = 'user-profile';
if (this.auth.authenticated && this.auth.userInfo) {
container.innerHTML = `
<div class="user-avatar">
${this.getInitials(this.auth.userInfo.name)}
</div>
<div class="user-details">
<div class="user-name">${this.auth.userInfo.name}</div>
<div class="user-email">${this.auth.userInfo.email}</div>
</div>
`;
}
return container;
}
// Create role-based element
createRoleBasedElement(roles, element, options = {}) {
const { requireAll = false, fallback = null } = options;
const hasAccess = requireAll ? this.auth.hasAllRoles(...roles) : this.auth.hasAnyRole(...roles);
if (hasAccess) {
return element;
} else if (fallback) {
return fallback;
} else {
return document.createComment('Insufficient permissions');
}
}
// Get user initials
getInitials(name) {
if (!name) return '?';
return name
.split(' ')
.map((part) => part[0])
.join('')
.toUpperCase()
.substring(0, 2);
}
// Show notification
showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('fade-out');
setTimeout(() => notification.remove(), 300);
}, 3000);
}
}
API Client
Section titled “API Client”// js/api-client.js
class APIClient {
constructor(authManager, baseURL) {
this.auth = authManager;
this.baseURL = baseURL;
}
// Generic request method
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
try {
const response = await this.auth.fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
}
return await response.text();
} catch (error) {
console.error('API request failed:', error);
throw error;
}
}
// GET request
async get(endpoint, params = {}) {
const queryString = new URLSearchParams(params).toString();
const url = queryString ? `${endpoint}?${queryString}` : endpoint;
return this.request(url, { method: 'GET' });
}
// POST request
async post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data),
});
}
// PUT request
async put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data),
});
}
// DELETE request
async delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
// PATCH request
async patch(endpoint, data) {
return this.request(endpoint, {
method: 'PATCH',
body: JSON.stringify(data),
});
}
}
Complete Application Example
Section titled “Complete Application Example”// js/main.js
(async function () {
// Configuration
const config = {
keycloak: {
url: 'https://your-cluster-id.app.skycloak.io',
realm: 'your-realm',
clientId: 'your-javascript-app',
},
api: {
baseURL: 'https://api.example.com',
},
};
// Initialize auth manager
const authManager = new AuthManager(config.keycloak);
const ui = new UIComponents(authManager);
const api = new APIClient(authManager, config.api.baseURL);
// Setup event handlers
authManager.on('onAuthenticated', async (userInfo) => {
console.log('User authenticated:', userInfo);
await loadUserDashboard();
});
authManager.on('onLogout', () => {
console.log('User logged out');
showPublicContent();
});
authManager.on('onTokenRefreshed', () => {
console.log('Token refreshed');
});
authManager.on('onAuthError', (error) => {
console.error('Authentication error:', error);
ui.showNotification('Authentication failed', 'error');
});
// Initialize authentication
try {
const authenticated = await authManager.init();
if (authenticated) {
await loadUserDashboard();
} else {
showPublicContent();
}
} catch (error) {
console.error('Failed to initialize auth:', error);
showErrorPage();
}
// Load user dashboard
async function loadUserDashboard() {
const container = document.getElementById('app-content');
container.innerHTML = '';
// Add user profile
container.appendChild(ui.createUserProfile());
// Add logout button
container.appendChild(ui.createLogoutButton());
// Load user-specific content
try {
const userData = await api.get('/user/profile');
displayUserData(userData);
} catch (error) {
ui.showNotification('Failed to load user data', 'error');
}
// Show role-based content
if (authManager.hasRole('admin')) {
container.appendChild(createAdminPanel());
}
if (authManager.hasAnyRole('editor', 'moderator')) {
container.appendChild(createContentManagement());
}
}
// Show public content
function showPublicContent() {
const container = document.getElementById('app-content');
container.innerHTML = `
<h1>Welcome to Our Application</h1>
<p>Please log in to access your dashboard.</p>
`;
container.appendChild(
ui.createLoginButton({
text: 'Sign In with Skycloak',
className: 'btn btn-primary btn-lg',
})
);
}
// Show error page
function showErrorPage() {
const container = document.getElementById('app-content');
container.innerHTML = `
<h1>Error</h1>
<p>Sorry, something went wrong. Please try again later.</p>
`;
}
// Display user data
function displayUserData(userData) {
const dataContainer = document.createElement('div');
dataContainer.className = 'user-data';
dataContainer.innerHTML = `
<h2>Your Profile</h2>
<dl>
<dt>Name</dt>
<dd>${userData.name || 'Not set'}</dd>
<dt>Email</dt>
<dd>${userData.email}</dd>
<dt>Member Since</dt>
<dd>${new Date(userData.createdAt).toLocaleDateString()}</dd>
</dl>
`;
document.getElementById('app-content').appendChild(dataContainer);
}
// Create admin panel
function createAdminPanel() {
const panel = document.createElement('div');
panel.className = 'admin-panel';
panel.innerHTML = `
<h2>Admin Panel</h2>
<button onclick="manageUsers()">Manage Users</button>
<button onclick="viewSystemStats()">System Stats</button>
`;
return panel;
}
// Create content management
function createContentManagement() {
const panel = document.createElement('div');
panel.className = 'content-management';
panel.innerHTML = `
<h2>Content Management</h2>
<button onclick="createContent()">Create New</button>
<button onclick="viewContent()">View All</button>
`;
return panel;
}
// Global functions for demo
window.manageUsers = async () => {
try {
const users = await api.get('/admin/users');
console.log('Users:', users);
ui.showNotification('Loaded user list', 'success');
} catch (error) {
ui.showNotification('Failed to load users', 'error');
}
};
window.viewSystemStats = async () => {
try {
const stats = await api.get('/admin/stats');
console.log('Stats:', stats);
ui.showNotification('Loaded system stats', 'success');
} catch (error) {
ui.showNotification('Failed to load stats', 'error');
}
};
window.createContent = () => {
ui.showNotification('Create content feature coming soon', 'info');
};
window.viewContent = async () => {
try {
const content = await api.get('/content');
console.log('Content:', content);
ui.showNotification('Loaded content list', 'success');
} catch (error) {
ui.showNotification('Failed to load content', 'error');
}
};
})();
Single Page Application (SPA) Support
Section titled “Single Page Application (SPA) Support”// js/spa-router.js
class SPARouter {
constructor(authManager) {
this.auth = authManager;
this.routes = new Map();
this.currentRoute = null;
// Listen for popstate events
window.addEventListener('popstate', () => this.handleRoute());
}
// Register a route
register(path, handler, options = {}) {
this.routes.set(path, {
handler,
requiresAuth: options.requiresAuth || false,
roles: options.roles || [],
});
}
// Navigate to a route
navigate(path) {
window.history.pushState(null, '', path);
this.handleRoute();
}
// Handle current route
async handleRoute() {
const path = window.location.pathname;
const route = this.routes.get(path) || this.routes.get('*');
if (!route) {
console.error('No route found for:', path);
return;
}
// Check authentication
if (route.requiresAuth && !this.auth.authenticated) {
// Store intended route
sessionStorage.setItem('redirectPath', path);
this.auth.login();
return;
}
// Check roles
if (route.roles.length > 0) {
const hasAccess = route.roles.some((role) => this.auth.hasRole(role));
if (!hasAccess) {
this.navigate('/unauthorized');
return;
}
}
// Execute route handler
this.currentRoute = path;
await route.handler();
}
// Get current route
getCurrentRoute() {
return this.currentRoute;
}
}
// Usage example
const router = new SPARouter(authManager);
// Register routes
router.register('/', () => {
document.getElementById('content').innerHTML = '<h1>Home Page</h1>';
});
router.register(
'/profile',
async () => {
const userData = await api.get('/user/profile');
document.getElementById('content').innerHTML = `
<h1>User Profile</h1>
<p>Name: ${userData.name}</p>
<p>Email: ${userData.email}</p>
`;
},
{ requiresAuth: true }
);
router.register(
'/admin',
() => {
document.getElementById('content').innerHTML = '<h1>Admin Dashboard</h1>';
},
{ requiresAuth: true, roles: ['admin'] }
);
router.register('/unauthorized', () => {
document.getElementById('content').innerHTML = '<h1>Access Denied</h1>';
});
router.register('*', () => {
document.getElementById('content').innerHTML = '<h1>404 - Page Not Found</h1>';
});
// Handle initial route
router.handleRoute();
CSS Styles
Section titled “CSS Styles”/* styles.css */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
}
#app {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.btn-primary {
background-color: #007bff;
color: white;
}
.btn-primary:hover {
background-color: #0056b3;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover {
background-color: #545b62;
}
.user-profile {
display: flex;
align-items: center;
gap: 15px;
padding: 20px;
background-color: white;
border-radius: 8px;
margin-bottom: 20px;
}
.user-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #007bff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
}
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 4px;
color: white;
animation: slideIn 0.3s ease-out;
z-index: 1000;
}
.notification-info {
background-color: #17a2b8;
}
.notification-success {
background-color: #28a745;
}
.notification-error {
background-color: #dc3545;
}
.notification.fade-out {
animation: fadeOut 0.3s ease-out forwards;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fadeOut {
to {
opacity: 0;
transform: translateY(-20px);
}
}
#loading {
text-align: center;
padding: 40px;
color: #666;
}
.admin-panel,
.content-management {
background-color: white;
padding: 20px;
border-radius: 8px;
margin-top: 20px;
}
.admin-panel button,
.content-management button {
margin-right: 10px;
}
Production Considerations
Section titled “Production Considerations”Security Best Practices
Section titled “Security Best Practices”// js/security.js
class SecurityManager {
constructor() {
this.setupCSP();
this.preventClickjacking();
this.sanitizer = this.createSanitizer();
}
// Setup Content Security Policy
setupCSP() {
const meta = document.createElement('meta');
meta.httpEquiv = 'Content-Security-Policy';
meta.content = `
default-src 'self';
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' 'unsafe-inline';
connect-src 'self' https://*.skycloak.io https://api.example.com;
frame-src 'self' https://*.skycloak.io;
img-src 'self' data: https:;
`.trim();
document.head.appendChild(meta);
}
// Prevent clickjacking
preventClickjacking() {
if (window.top !== window.self) {
window.top.location = window.self.location;
}
}
// Create HTML sanitizer
createSanitizer() {
const config = {
allowedTags: ['b', 'i', 'em', 'strong', 'a', 'p', 'br'],
allowedAttributes: {
a: ['href', 'title', 'target'],
},
allowedSchemes: ['http', 'https', 'mailto'],
};
return (html) => {
// Basic sanitization - use DOMPurify in production
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
};
}
// Validate URL
isValidUrl(url) {
try {
const parsed = new URL(url);
return ['http:', 'https:'].includes(parsed.protocol);
} catch {
return false;
}
}
// Generate nonce for inline scripts
generateNonce() {
const array = new Uint8Array(16);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array));
}
}
Performance Optimization
Section titled “Performance Optimization”// js/performance.js
class PerformanceOptimizer {
constructor() {
this.cache = new Map();
this.pendingRequests = new Map();
}
// Cache API responses
async cachedFetch(url, options = {}, ttl = 300000) {
// 5 minutes default
const cacheKey = `${url}-${JSON.stringify(options)}`;
// Check cache
const cached = this.cache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data;
}
// Check for pending request
if (this.pendingRequests.has(cacheKey)) {
return this.pendingRequests.get(cacheKey);
}
// Make request
const promise = fetch(url, options)
.then((response) => response.json())
.then((data) => {
this.cache.set(cacheKey, {
data,
timestamp: Date.now(),
});
this.pendingRequests.delete(cacheKey);
return data;
})
.catch((error) => {
this.pendingRequests.delete(cacheKey);
throw error;
});
this.pendingRequests.set(cacheKey, promise);
return promise;
}
// Debounce function calls
debounce(func, delay) {
let timeoutId;
return (...args) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
}
// Throttle function calls
throttle(func, limit) {
let inThrottle;
return (...args) => {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// Lazy load images
lazyLoadImages() {
const images = document.querySelectorAll('img[data-src]');
const imageObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.removeAttribute('data-src');
imageObserver.unobserve(img);
}
});
});
images.forEach((img) => imageObserver.observe(img));
}
// Preload critical resources
preloadResources() {
const resources = [
{ href: '/css/critical.css', as: 'style' },
{ href: '/js/keycloak.min.js', as: 'script' },
{
href: 'https://your-cluster-id.app.skycloak.io/realms/your-realm/.well-known/openid-configuration',
as: 'fetch',
crossOrigin: 'anonymous',
},
];
resources.forEach((resource) => {
const link = document.createElement('link');
link.rel = 'preload';
Object.assign(link, resource);
document.head.appendChild(link);
});
}
}
Troubleshooting
Section titled “Troubleshooting”Common Issues and Solutions
Section titled “Common Issues and Solutions”// js/troubleshoot.js
class TroubleshootingHelper {
constructor(authManager) {
this.auth = authManager;
this.issues = [];
}
// Run diagnostics
async runDiagnostics() {
console.log('Running authentication diagnostics...');
// Check browser compatibility
this.checkBrowserCompatibility();
// Check third-party cookies
await this.checkThirdPartyCookies();
// Check Keycloak connectivity
await this.checkKeycloakConnectivity();
// Check token status
this.checkTokenStatus();
// Report results
this.reportResults();
}
checkBrowserCompatibility() {
const required = ['Promise', 'fetch', 'localStorage', 'sessionStorage'];
const missing = required.filter((feature) => !(feature in window));
if (missing.length > 0) {
this.issues.push({
type: 'error',
message: `Browser missing required features: ${missing.join(', ')}`,
});
}
}
async checkThirdPartyCookies() {
try {
// Test if third-party cookies are enabled
const testKey = 'cookie-test';
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = `${this.auth.keycloak.authServerUrl}/test-cookies.html`;
document.body.appendChild(iframe);
// Clean up
setTimeout(() => iframe.remove(), 1000);
} catch (error) {
this.issues.push({
type: 'warning',
message: 'Third-party cookies may be disabled',
});
}
}
async checkKeycloakConnectivity() {
try {
const configUrl = `${this.auth.keycloak.authServerUrl}/realms/${this.auth.keycloak.realm}/.well-known/openid-configuration`;
const response = await fetch(configUrl);
if (!response.ok) {
this.issues.push({
type: 'error',
message: `Keycloak server returned ${response.status}`,
});
}
} catch (error) {
this.issues.push({
type: 'error',
message: `Cannot connect to Keycloak: ${error.message}`,
});
}
}
checkTokenStatus() {
if (this.auth.authenticated) {
const token = this.auth.keycloak.tokenParsed;
const now = Math.floor(Date.now() / 1000);
const expiresIn = token.exp - now;
if (expiresIn < 300) {
// Less than 5 minutes
this.issues.push({
type: 'warning',
message: `Token expires in ${Math.floor(expiresIn / 60)} minutes`,
});
}
}
}
reportResults() {
if (this.issues.length === 0) {
console.log('✅ All diagnostics passed');
} else {
console.group('⚠️ Diagnostics found issues:');
this.issues.forEach((issue) => {
console[issue.type === 'error' ? 'error' : 'warn'](issue.message);
});
console.groupEnd();
}
}
}
// Usage
const troubleshooter = new TroubleshootingHelper(authManager);
// Run diagnostics on demand or when issues occur
window.runAuthDiagnostics = () => troubleshooter.runDiagnostics();
Next Steps
Section titled “Next Steps”- Configure Security Settings - Add extra security layers to your application
- Set Up Applications - Configure your JavaScript app client settings
- User Management - Manage users and their access
- Explore Other Integrations - See integration guides for other platforms