Plain JavaScript Integration
Plain JavaScript Integration
This guide covers how to integrate Skycloak authentication into plain JavaScript applications without any framework dependencies.
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
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
<!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
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
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
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
// 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
// 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
// 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
// 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
/* 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
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
// 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
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
- 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