PHPackages                             earnon/keycloak-auth - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. earnon/keycloak-auth

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

earnon/keycloak-auth
====================

Laravel integration package for Keycloak authentication with HTMX support

032PHP

Since Oct 1Pushed 7mo agoCompare

[ Source](https://github.com/syedasgarahmed/keycloak-htmx-laravel)[ Packagist](https://packagist.org/packages/earnon/keycloak-auth)[ RSS](/packages/earnon-keycloak-auth/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Laravel Keycloak with HTMX Integration
======================================

[](#laravel-keycloak-with-htmx-integration)

A seamless integration of Keycloak authentication with Laravel, enhanced with HTMX for dynamic, single-page application-like experiences.

Prerequisites
-------------

[](#prerequisites)

- PHP 8.0 or higher
- Laravel 9.x or 10.x
- Node.js &amp; NPM (for frontend assets)
- Keycloak server (version 15 or later recommended)

Installation
------------

[](#installation)

1. Install the package via Composer:

```
composer require maxjack/keycloak-auth
```

2. Configure your `.env` file with your Keycloak settings: Note : KEYCLOAK\_DEFAULT\_REDIRECT assign the path where you want to redirect after successful login

```
KEYCLOAK_BASE_URL=https://auth.keycloak.ai
KEYCLOAK_REALM=realm
KEYCLOAK_CLIENT_ID=client
KEYCLOAK_CLIENT_SECRET=secret
KEYCLOAK_REDIRECT_URI=${APP_URL}/auth/callback
KEYCLOAK_HTMX_ENABLED=true
KEYCLOAK_DEFAULT_REDIRECT=/dashboard
```

note : add the config to env file before executing the next step

3. Publish the configuration file:

```
php artisan vendor:publish --tag=keycloak-config

php artisan vendor:publish --tag=public

php artisan vendor:publish --tag=keycloak-controller
```

Frontend Setup
--------------

[](#frontend-setup)

### Required JavaScript Libraries

[](#required-javascript-libraries)

Include these scripts in your main layout file (usually `resources/views/layouts/app.blade.php`):

```

```

Example Views
-------------

[](#example-views)

Include these scripts where you want to show the login (usually `resources/views/welcome.blade.php`):

### resources/views/welcome.blade.php

[](#resourcesviewswelcomebladephp)

```

                                    Loading...

                                Loading secure login...

                            // Authentication Handler - Namespace to prevent conflicts
                            window.AuthHandler = (function() {
                                'use strict';

                                // Private variables
                                let container = null;
                                let isInitialized = false;

                                // Configuration
                                const CONFIG = {
                                    containerId: 'kc-container',
                                    loaderOverlayId: 'kc-loader-overlay',
                                    zIndex: '1050',
                                    keycloakRoute: '{{ route("keycloak.proxy") }}'
                                };

                                // CSRF token injection
                                const csrfHandler = {
                                    addCsrfToForms: function(root = document) {
                                        const tokenMeta = utils.safeQuerySelector('meta[name="csrf-token"]');
                                        if (!tokenMeta) return;
                                        const csrfToken = tokenMeta.getAttribute("content");

                                        utils.safeQuerySelectorAll("form", root).forEach(form => {
                                            if (!form.querySelector("input[name='_token']")) {
                                                let hiddenInput = document.createElement("input");
                                                hiddenInput.type = "hidden";
                                                hiddenInput.name = "_token";
                                                hiddenInput.value = csrfToken;
                                                form.appendChild(hiddenInput);
                                            }
                                        });
                                    }
                                };

                                // HTMX integration
                                const htmxIntegration = {
                                    rewireLinks: function(context) {
                                        if (!context) return;

                                        const links = utils.safeQuerySelectorAll('a[href]', context);

                                        links.forEach(link => {
                                            const href = link.getAttribute('href');
                                            if (href && !href.startsWith('javascript:') && !link.dataset.authProcessed) {
                                                try {
                                                    const proxyUrl = `${CONFIG.keycloakRoute}?url=${encodeURIComponent(href)}`;
                                                    link.setAttribute('hx-get', proxyUrl);
                                                    link.setAttribute('hx-target', `#${CONFIG.containerId}`);
                                                    link.setAttribute('hx-swap', 'innerHTML');
                                                    link.removeAttribute('href');
                                                    link.classList.add('keycloak-link');
                                                    link.dataset.authProcessed = 'true';
                                                } catch (e) {
                                                    console.error('AuthHandler: Error processing link', href, e);
                                                }
                                            }
                                        });
                                    },

                                    rewireForms: function(context) {
                                        if (!context) return;

                                        const forms = utils.safeQuerySelectorAll('form[action]', context);

                                        csrfHandler.addCsrfToForms(context);

                                        forms.forEach(form => {
                                            if (form.dataset.authProcessed) return;

                                            const action = form.getAttribute('action');
                                            const method = (form.getAttribute('method') || 'GET').toUpperCase();

                                            if (action) {
                                                try {
                                                    console.log(CONFIG.keycloakRoute+'?url='+encodeURIComponent(action))
                                                    const proxyUrl = `${CONFIG.keycloakRoute}?url=${encodeURIComponent(action)}`;

                                                    if (method === 'POST') {
                                                        form.setAttribute('hx-post', proxyUrl);
                                                    } else {
                                                        form.setAttribute('hx-get', proxyUrl);
                                                    }

                                                    form.setAttribute('hx-target', `#${CONFIG.containerId}`);
                                                    form.setAttribute('hx-swap', 'innerHTML');
                                                    form.removeAttribute('action');
                                                    form.dataset.authProcessed = 'true';
                                                } catch (e) {
                                                    console.error('AuthHandler: Error processing form', action, e);
                                                }
                                            }
                                        });
                                    },

                                    processContent: function(context) {
                                        if (!context) return;

                                        this.rewireLinks(context);
                                        this.rewireForms(context);
                                        passwordToggle.init(context);

                                        // Process with HTMX if available
                                        if (window.htmx && typeof window.htmx.process === 'function') {
                                            try {
                                                window.htmx.process(context);
                                            } catch (e) {
                                                console.error('AuthHandler: Error processing HTMX', e);
                                            }
                                        }
                                    }
                                };

                                // Private utility functions
                                const utils = {
                                    // Safe element selection
                                    safeQuerySelector: function(selector, context = document) {
                                        try {
                                            return context.querySelector(selector);
                                        } catch (e) {
                                            console.warn(`AuthHandler: Invalid selector "${selector}"`, e);
                                            return null;
                                        }
                                    },

                                    // Safe element selection (multiple)
                                    safeQuerySelectorAll: function(selector, context = document) {
                                        try {
                                            return Array.from(context.querySelectorAll(selector));
                                        } catch (e) {
                                            console.warn(`AuthHandler: Invalid selector "${selector}"`, e);
                                            return [];
                                        }
                                    },

                                    // Create element with attributes
                                    createElement: function(tag, attributes = {}, innerHTML = '') {
                                        const element = document.createElement(tag);

                                        Object.keys(attributes).forEach(key => {
                                            if (key === 'className') {
                                                element.className = attributes[key];
                                            } else if (key === 'style' && typeof attributes[key] === 'object') {
                                                Object.assign(element.style, attributes[key]);
                                            } else {
                                                element.setAttribute(key, attributes[key]);
                                            }
                                        });

                                        if (innerHTML) {
                                            element.innerHTML = innerHTML;
                                        }

                                        return element;
                                    },

                                    // Debounce function to prevent rapid calls
                                    debounce: function(func, wait) {
                                        let timeout;
                                        return function executedFunction(...args) {
                                            const later = () => {
                                                clearTimeout(timeout);
                                                func(...args);
                                            };
                                            clearTimeout(timeout);
                                            timeout = setTimeout(later, wait);
                                        };
                                    }
                                };

                                // Loader management
                                const loader = {
                                    show: function() {
                                        if (!container) {
                                            console.warn('AuthHandler: Container not found for loader');
                                            return;
                                        }

                                        // Prevent multiple loaders
                                        if (utils.safeQuerySelector(`#${CONFIG.loaderOverlayId}`)) {
                                            return;
                                        }

                                        const loaderElement = utils.createElement('div', {
                                            id: CONFIG.loaderOverlayId,
                                            className: 'position-absolute top-0 start-0 w-100 h-100 d-flex justify-content-center align-items-center bg-white',
                                            style: { zIndex: CONFIG.zIndex }
                                        }, `

                        Loading...

                    Processing...

            `);

                                        container.appendChild(loaderElement);
                                    },

                                    hide: function() {
                                        const loaderElement = utils.safeQuerySelector(`#${CONFIG.loaderOverlayId}`);
                                        if (loaderElement && loaderElement.parentNode) {
                                            loaderElement.parentNode.removeChild(loaderElement);
                                        }
                                    }
                                };

                                // Error handling
                                const errorHandler = {
                                    show: function(message) {
                                        if (!container) {
                                            console.error('AuthHandler: Container not found for error display');
                                            return;
                                        }

                                        const alertElement = utils.createElement('div', {
                                            className: 'alert alert-danger alert-dismissible fade show m-2',
                                            role: 'alert'
                                        }, `
                Error: ${message}

            `);

                                        container.insertBefore(alertElement, container.firstChild);

                                        // Auto-remove after 5 seconds
                                        setTimeout(() => {
                                            if (alertElement && alertElement.parentNode) {
                                                alertElement.parentNode.removeChild(alertElement);
                                            }
                                        }, 5000);
                                    }
                                };

                                // Password toggle functionality
                                const passwordToggle = {
                                    icons: {
                                        show: `

            `,
                                        hide: `

            `
                                    },

                                    init: function(context) {
                                        if (!context) return;

                                        const toggleButtons = utils.safeQuerySelectorAll(
                                            "button[aria-label='Show password'], button[aria-label='Hide password']",
                                            context
                                        );

                                        toggleButtons.forEach(btn => {
                                            if (btn.dataset.authToggleBound) {
                                                return; // Already initialized
                                            }

                                            const inputId = btn.getAttribute('aria-controls');
                                            const input = inputId ? utils.safeQuerySelector(`#${inputId}`, context) : null;

                                            if (!input) {
                                                console.warn('AuthHandler: Password input not found for toggle button');
                                                return;
                                            }

                                            // Set initial state
                                            btn.innerHTML = this.icons.show;

                                            // Create toggle handler
                                            const toggleHandler = () => {
                                                try {
                                                    if (input.type === 'password') {
                                                        input.type = 'text';
                                                        btn.setAttribute('aria-label', 'Hide password');
                                                        btn.innerHTML = this.icons.hide;
                                                    } else {
                                                        input.type = 'password';
                                                        btn.setAttribute('aria-label', 'Show password');
                                                        btn.innerHTML = this.icons.show;
                                                    }
                                                } catch (e) {
                                                    console.error('AuthHandler: Error toggling password visibility', e);
                                                }
                                            };

                                            // Remove existing listeners to prevent duplicates
                                            btn.removeEventListener('click', btn._authToggleHandler);

                                            // Add new listener
                                            btn.addEventListener('click', toggleHandler);
                                            btn._authToggleHandler = toggleHandler;

                                            // Mark as initialized
                                            btn.dataset.authToggleBound = 'true';
                                        });
                                    }
                                };

                                // Event handlers
                                const eventHandlers = {
                                    beforeRequest: function() {
                                        loader.show();
                                    },

                                    afterSwap: function(event) {
                                        loader.hide();

                                        if (event.target && event.target.id === CONFIG.containerId) {
                                            htmxIntegration.processContent(event.target);
                                        }
                                    },

                                    responseError: function(event) {
                                        loader.hide();
                                        const message = (event.detail && event.detail.xhr && event.detail.xhr.statusText) ||
                                            'Something went wrong. Please try again.';
                                        errorHandler.show(message);
                                    },

                                    sendError: function() {
                                        loader.hide();
                                        errorHandler.show('Network error. Please check your connection.');
                                    }
                                };

                                // Initialization
                                const init = function() {
                                    if (isInitialized) {
                                        console.warn('AuthHandler: Already initialized');
                                        return false;
                                    }

                                    container = utils.safeQuerySelector(`#${CONFIG.containerId}`);
                                    if (!container) {
                                        console.error(`AuthHandler: Container with id "${CONFIG.containerId}" not found`);
                                        return false;
                                    }

                                    // Set up HTMX event listeners
                                    const eventMappings = [
                                        ['htmx:beforeRequest', eventHandlers.beforeRequest],
                                        ['htmx:afterSwap', eventHandlers.afterSwap],
                                        ['htmx:responseError', eventHandlers.responseError],
                                        ['htmx:sendError', eventHandlers.sendError]
                                    ];

                                    eventMappings.forEach(([eventName, handler]) => {
                                        // Remove existing listeners to prevent duplicates
                                        document.body.removeEventListener(eventName, handler);
                                        // Add new listener
                                        document.body.addEventListener(eventName, handler);
                                    });

                                    // Initial content processing
                                    htmxIntegration.processContent(container);

                                    isInitialized = true;
                                    console.log('AuthHandler: Initialized successfully');
                                    return true;
                                };

                                // Cleanup function
                                const destroy = function() {
                                    if (!isInitialized) return;

                                    const eventMappings = [
                                        ['htmx:beforeRequest', eventHandlers.beforeRequest],
                                        ['htmx:afterSwap', eventHandlers.afterSwap],
                                        ['htmx:responseError', eventHandlers.responseError],
                                        ['htmx:sendError', eventHandlers.sendError]
                                    ];

                                    eventMappings.forEach(([eventName, handler]) => {
                                        document.body.removeEventListener(eventName, handler);
                                    });

                                    loader.hide();
                                    isInitialized = false;
                                    container = null;

                                    console.log('AuthHandler: Destroyed successfully');
                                };

                                // Public API
                                return {
                                    init: init,
                                    destroy: destroy,
                                    isInitialized: function() { return isInitialized; },

                                    // Utility methods that can be used externally
                                    showLoader: loader.show,
                                    hideLoader: loader.hide,
                                    showError: errorHandler.show,

                                    // Manual content processing
                                    processContent: htmxIntegration.processContent,

                                    // Configuration access
                                    getConfig: function() { return Object.assign({}, CONFIG); },
                                    setConfig: function(newConfig) {
                                        if (isInitialized) {
                                            console.warn('AuthHandler: Cannot change configuration after initialization');
                                            return false;
                                        }
                                        Object.assign(CONFIG, newConfig);
                                        return true;
                                    }
                                };
                            })();

                            // Auto-initialize when DOM is ready
                            (function() {
                                if (document.readyState === 'loading') {
                                    document.addEventListener('DOMContentLoaded', function() {
                                        AuthHandler.init();
                                    });
                                } else {
                                    // DOM is already ready
                                    AuthHandler.init();
                                }
                            })();

```

---

### Stop here setup is done

[](#stop-here-setup-is-done)

---

### Example Login Button

[](#example-login-button)

```

    Login with Keycloak

```

### HTMX Configuration

[](#htmx-configuration)

Add this script to initialize HTMX and handle authentication states:

```

document.addEventListener('DOMContentLoaded', function() {
    // Initialize HTMX
    htmx.defineExtension('auth-required', {
        onEvent: function(name, evt) {
            if (name === 'htmx:beforeRequest' && !isAuthenticated()) {
                window.location.href = '{{ route("keycloak.login") }}';
                return false;
            }
        }
    });

    // Check authentication status
    function isAuthenticated() {
        return {!! auth()->check() ? 'true' : 'false' !!};
    }
});

```

Configuration Options
---------------------

[](#configuration-options)

Edit `config/keycloak.php` to customize the behavior:

```
return [
    'base_url' => env('KEYCLOAK_BASE_URL'),
    'realm' => env('KEYCLOAK_REALM'),
    'client_id' => env('KEYCLOAK_CLIENT_ID'),
    'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
    'redirect_uri' => env('KEYCLOAK_REDIRECT_URI'),
    'htmx_enabled' => env('KEYCLOAK_HTMX_ENABLED', true),

    // Enable/disable features that should be shown in the UI
    'features' => [
        'registration' => true,  // Show registration link
        'forgot_password' => true,  // Show forgot password link
        'remember_me' => true,  // Show remember me checkbox
        'social_login' => true,  // Show social login buttons if configured in Keycloak
    ],

    // Routes configuration
    'routes' => [
        'login' => 'keycloak.login',
        'logout' => 'keycloak.logout',
        'register' => 'keycloak.register',
        'callback' => 'keycloak.callback',
    ],
];
```

Available Routes
----------------

[](#available-routes)

MethodURIActionDescriptionGET/auth/loginKeycloakAuthController@loginInitiate loginGET/auth/callbackKeycloakAuthController@callbackOAuth callback URLPOST/auth/logoutKeycloakAuthController@logoutLogout userGET/auth/userKeycloakAuthController@userGet current user infoPOST/auth/refreshKeycloakAuthController@refreshRefresh access tokenMiddleware
----------

[](#middleware)

Protect your routes using the included middleware:

```
// Single route
Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware('keycloak.auth');

// Route group
Route::middleware(['keycloak.auth'])->group(function () {
    // Protected routes here
});
```

Customization
-------------

[](#customization)

### Custom Views

[](#custom-views)

Publish the views to customize them:

```
php artisan vendor:publish --tag=keycloak-views
```

### Events

[](#events)

Listen for these events in your application:

```
// In your EventServiceProvider
protected $listen = [
    'keycloak.login' => [
        YourLoginListener::class,
    ],
    'keycloak.logout' => [
        YourLogoutListener::class,
    ],
];
```

Troubleshooting
---------------

[](#troubleshooting)

- **HTMX not working**: Ensure you've included the HTMX script before your custom scripts
- **CORS issues**: Configure CORS in your Keycloak realm settings
- **Session issues**: Verify your session driver in `config/session.php`
- **HTTPS required**: Make sure your application is served over HTTPS in production

Security
--------

[](#security)

- Always use HTTPS in production
- Keep your client secret secure
- Regularly rotate your client secrets
- Implement proper session handling
- Follow Keycloak's security best practices

License
-------

[](#license)

This package is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).

Example Routes
--------------

[](#example-routes)

```
// routes/web.php
