{"id":2668,"date":"2025-08-28T07:12:34","date_gmt":"2025-08-28T07:12:34","guid":{"rendered":"https:\/\/www.devopsconsulting.in\/blog\/?p=2668"},"modified":"2025-08-28T07:12:35","modified_gmt":"2025-08-28T07:12:35","slug":"complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro","status":"publish","type":"post","link":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/","title":{"rendered":"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro)"},"content":{"rendered":"\n<p>This comprehensive guide will set up Single Sign-On (SSO) across all your Laravel applications using Keycloak as the Identity Provider. When a user logs into one application, they will automatically be authenticated in all other applications.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"architecture-overview\">Architecture Overview<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Keycloak<\/strong>: Central Identity Provider (IdP)<\/li>\n\n\n\n<li><strong>Laravel App 1<\/strong>: Your custom Laravel application<\/li>\n\n\n\n<li><strong>Laravel App 2<\/strong>: Another Laravel application<\/li>\n\n\n\n<li><strong>Eventmie Pro<\/strong>: Event management system<\/li>\n\n\n\n<li><strong>SSO Flow<\/strong>: Login once \u2192 Access all applications seamlessly<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-1-keycloak-installation-and-configuration\">Part 1: Keycloak Installation and Configuration<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1.1: Install Keycloak<\/h2>\n\n\n\n<p>Using Docker (recommended for development):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code><em># Create a directory for Keycloak<\/em>\nmkdir keycloak-sso &amp;&amp; cd keycloak-sso\n\n<em># Run Keycloak with Docker<\/em>\ndocker run -d \\\n  --name keycloak-sso \\\n  -p 8080:8080 \\\n  -e KEYCLOAK_ADMIN=admin \\\n  -e KEYCLOAK_ADMIN_PASSWORD=admin123 \\\n  -v $(pwd)\/keycloak-data:\/opt\/keycloak\/data \\\n  quay.io\/keycloak\/keycloak:latest start-dev\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1.2: Configure Keycloak Realm<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Access Keycloak Admin Console<\/strong>: <a href=\"http:\/\/localhost:8080\/\" target=\"_blank\" rel=\"noreferrer noopener\">http:\/\/localhost:8080<\/a><\/li>\n\n\n\n<li><strong>Login<\/strong>: admin \/ admin123<\/li>\n\n\n\n<li><strong>Create New Realm<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Click &#8220;Create Realm&#8221;<\/li>\n\n\n\n<li>Name: <code>laravel-sso-realm<\/code><\/li>\n\n\n\n<li>Click &#8220;Create&#8221;<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1.3: Create Keycloak Client<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Navigate to Clients<\/strong> \u2192 Click &#8220;Create client&#8221;<\/li>\n\n\n\n<li><strong>Client Configuration<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Client type: <code>OpenID Connect<\/code><\/li>\n\n\n\n<li>Client ID: <code>laravel-multi-app-client<\/code><\/li>\n\n\n\n<li>Click &#8220;Next&#8221;<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Capability config<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Client authentication: <code>ON<\/code><\/li>\n\n\n\n<li>Authorization: <code>OFF<\/code><\/li>\n\n\n\n<li>Standard flow: <code>ON<\/code><\/li>\n\n\n\n<li>Direct access grants: <code>ON<\/code><\/li>\n\n\n\n<li>Click &#8220;Next&#8221;<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Login settings<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Valid redirect URIs: text<code>http:\/\/laravel-app1.local\/auth\/keycloak\/callback http:\/\/laravel-app2.local\/auth\/keycloak\/callback http:\/\/eventmie.local\/auth\/keycloak\/callback http:\/\/eventmie.local\/admin\/auth\/keycloak\/callback<\/code><\/li>\n\n\n\n<li>Valid post logout redirect URIs: text<code>http:\/\/laravel-app1.local\/ http:\/\/laravel-app2.local\/ http:\/\/eventmie.local\/<\/code><\/li>\n\n\n\n<li>Web origins: <code>*<\/code><\/li>\n\n\n\n<li>Click &#8220;Save&#8221;<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Get Client Secret<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Go to &#8220;Credentials&#8221; tab<\/li>\n\n\n\n<li>Copy the &#8220;Client secret&#8221; (you&#8217;ll need this later)<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1.4: Configure User Roles<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Create Realm Roles<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Go to &#8220;Realm roles&#8221; \u2192 Click &#8220;Create role&#8221;<\/li>\n\n\n\n<li>Create these roles:\n<ul class=\"wp-block-list\">\n<li><code>app_user<\/code> (basic user)<\/li>\n\n\n\n<li><code>app_admin<\/code> (admin across all apps)<\/li>\n\n\n\n<li><code>eventmie_organizer<\/code> (Eventmie organizer)<\/li>\n\n\n\n<li><code>eventmie_admin<\/code> (Eventmie admin)<\/li>\n\n\n\n<li><code>eventmie_customer<\/code> (Eventmie customer)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Create Test Users<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Go to &#8220;Users&#8221; \u2192 Click &#8220;Create new user&#8221;<\/li>\n\n\n\n<li>Username: <code>testuser<\/code><\/li>\n\n\n\n<li>Email: <code>test@example.com<\/code><\/li>\n\n\n\n<li>First name: <code>Test<\/code><\/li>\n\n\n\n<li>Last name: <code>User<\/code><\/li>\n\n\n\n<li>Click &#8220;Create&#8221;<\/li>\n\n\n\n<li>Go to &#8220;Credentials&#8221; tab \u2192 Set password \u2192 Turn off &#8220;Temporary&#8221;<\/li>\n\n\n\n<li>Go to &#8220;Role mapping&#8221; tab \u2192 Assign roles<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-2-prepare-all-laravel-applications\">Part 2: Prepare All Laravel Applications<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2.1: Install Required Packages (Do this for ALL Laravel apps)<\/h2>\n\n\n\n<p>For each Laravel application:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code><em># Navigate to each Laravel project directory and run:<\/em>\ncomposer require laravel\/socialite\ncomposer require socialiteproviders\/keycloak\ncomposer require guzzlehttp\/guzzle\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2.2: Environment Configuration (All Apps)<\/h2>\n\n\n\n<p><strong>For Laravel App 1<\/strong> (<code>laravel-app1\/.env<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>APP_NAME=\"Laravel App 1\"\nAPP_URL=http:\/\/laravel-app1.local\n\n# Keycloak Configuration\nKEYCLOAK_BASE_URL=http:\/\/localhost:8080\nKEYCLOAK_REALM=laravel-sso-realm\nKEYCLOAK_CLIENT_ID=laravel-multi-app-client\nKEYCLOAK_CLIENT_SECRET=your-client-secret-from-keycloak\nKEYCLOAK_REDIRECT_URI=\"http:\/\/laravel-app1.local\/auth\/keycloak\/callback\"\n\n# Session Configuration for SSO\nSESSION_DRIVER=database\nSESSION_DOMAIN=.local\nSESSION_LIFETIME=120\nSESSION_ENCRYPT=false\n<\/code><\/pre>\n\n\n\n<p><strong>For Laravel App 2<\/strong> (<code>laravel-app2\/.env<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>APP_NAME=\"Laravel App 2\"\nAPP_URL=http:\/\/laravel-app2.local\n\n# Keycloak Configuration\nKEYCLOAK_BASE_URL=http:\/\/localhost:8080\nKEYCLOAK_REALM=laravel-sso-realm\nKEYCLOAK_CLIENT_ID=laravel-multi-app-client\nKEYCLOAK_CLIENT_SECRET=your-client-secret-from-keycloak\nKEYCLOAK_REDIRECT_URI=\"http:\/\/laravel-app2.local\/auth\/keycloak\/callback\"\n\n# Session Configuration for SSO\nSESSION_DRIVER=database\nSESSION_DOMAIN=.local\nSESSION_LIFETIME=120\nSESSION_ENCRYPT=false\n<\/code><\/pre>\n\n\n\n<p><strong>For Eventmie Pro<\/strong> (<code>eventmie\/.env<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>APP_NAME=\"Eventmie Pro\"\nAPP_URL=http:\/\/eventmie.local\n\n# Keycloak Configuration\nKEYCLOAK_BASE_URL=http:\/\/localhost:8080\nKEYCLOAK_REALM=laravel-sso-realm\nKEYCLOAK_CLIENT_ID=laravel-multi-app-client\nKEYCLOAK_CLIENT_SECRET=your-client-secret-from-keycloak\nKEYCLOAK_REDIRECT_URI=\"http:\/\/eventmie.local\/auth\/keycloak\/callback\"\nKEYCLOAK_ADMIN_REDIRECT_URI=\"http:\/\/eventmie.local\/admin\/auth\/keycloak\/callback\"\n\n# Session Configuration for SSO\nSESSION_DRIVER=database\nSESSION_DOMAIN=.local\nSESSION_LIFETIME=120\nSESSION_ENCRYPT=false\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2.3: Services Configuration (All Apps)<\/h2>\n\n\n\n<p>Add to <code>config\/services.php<\/code> in ALL applications:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ config\/services.php<\/em>\n\nreturn [\n    <em>\/\/ ... existing services<\/em>\n    \n    'keycloak' =&gt; [\n        'client_id' =&gt; env('KEYCLOAK_CLIENT_ID'),\n        'client_secret' =&gt; env('KEYCLOAK_CLIENT_SECRET'),\n        'redirect' =&gt; env('KEYCLOAK_REDIRECT_URI'),\n        'base_url' =&gt; env('KEYCLOAK_BASE_URL'),\n        'realm' =&gt; env('KEYCLOAK_REALM'),\n    ],\n];\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2.4: EventServiceProvider Configuration (All Apps)<\/h2>\n\n\n\n<p>Update <code>app\/Providers\/EventServiceProvider.php<\/code> in ALL applications:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ app\/Providers\/EventServiceProvider.php<\/em>\n\nnamespace App\\Providers;\n\nuse Illuminate\\Foundation\\Support\\Providers\\EventServiceProvider as ServiceProvider;\n\nclass EventServiceProvider extends ServiceProvider\n{\n    protected $listen = [\n        \\SocialiteProviders\\Manager\\SocialiteWasCalled::class =&gt; [\n            'SocialiteProviders\\\\Keycloak\\\\KeycloakExtendSocialite@handle',\n        ],\n    ];\n\n    public function boot()\n    {\n        <em>\/\/<\/em>\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-3-database-setup-all-apps\">Part 3: Database Setup (All Apps)<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3.1: User Migration (All Apps)<\/h2>\n\n\n\n<p>Create migration to add Keycloak fields:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code><em># Run in each Laravel app directory<\/em>\nphp artisan make:migration add_keycloak_fields_to_users_table\n<\/code><\/pre>\n\n\n\n<p><strong>For Standard Laravel Apps<\/strong> (<code>database\/migrations\/xxx_add_keycloak_fields_to_users_table.php<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nclass AddKeycloakFieldsToUsersTable extends Migration\n{\n    public function up()\n    {\n        Schema::table('users', function (Blueprint $table) {\n            $table-&gt;string('keycloak_id')-&gt;nullable()-&gt;unique();\n            $table-&gt;json('keycloak_roles')-&gt;nullable();\n            $table-&gt;timestamp('last_keycloak_sync')-&gt;nullable();\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('users', function (Blueprint $table) {\n            $table-&gt;dropColumn(['keycloak_id', 'keycloak_roles', 'last_keycloak_sync']);\n        });\n    }\n}\n<\/code><\/pre>\n\n\n\n<p><strong>For Eventmie Pro<\/strong> (same migration but ensure compatibility with Eventmie&#8217;s user structure):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n\nuse Illuminate\\Database\\Migrations\\Migration;\nuse Illuminate\\Database\\Schema\\Blueprint;\nuse Illuminate\\Support\\Facades\\Schema;\n\nclass AddKeycloakFieldsToUsersTable extends Migration\n{\n    public function up()\n    {\n        Schema::table('users', function (Blueprint $table) {\n            $table-&gt;string('keycloak_id')-&gt;nullable()-&gt;unique();\n            $table-&gt;json('keycloak_roles')-&gt;nullable();\n            $table-&gt;timestamp('last_keycloak_sync')-&gt;nullable();\n            \n            <em>\/\/ Ensure Eventmie-specific fields exist<\/em>\n            if (!Schema::hasColumn('users', 'is_organiser')) {\n                $table-&gt;boolean('is_organiser')-&gt;default(false);\n            }\n            if (!Schema::hasColumn('users', 'is_admin')) {\n                $table-&gt;boolean('is_admin')-&gt;default(false);\n            }\n        });\n    }\n\n    public function down()\n    {\n        Schema::table('users', function (Blueprint $table) {\n            $table-&gt;dropColumn(['keycloak_id', 'keycloak_roles', 'last_keycloak_sync']);\n        });\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3.2: Session Tables (All Apps)<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code><em># Run in each Laravel app directory<\/em>\nphp artisan session:table\nphp artisan migrate\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-4-sso-service-class-create-in-all-apps\">Part 4: SSO Service Class (Create in All Apps)<\/h2>\n\n\n\n<p>Create a shared SSO service in each app at <code>app\/Services\/SSOService.php<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ app\/Services\/SSOService.php<\/em>\n\nnamespace App\\Services;\n\nuse GuzzleHttp\\Client;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass SSOService\n{\n    protected $client;\n    protected $keycloakBaseUrl;\n    protected $realm;\n    protected $clientId;\n    protected $clientSecret;\n\n    public function __construct()\n    {\n        $this-&gt;client = new Client();\n        $this-&gt;keycloakBaseUrl = env('KEYCLOAK_BASE_URL');\n        $this-&gt;realm = env('KEYCLOAK_REALM');\n        $this-&gt;clientId = env('KEYCLOAK_CLIENT_ID');\n        $this-&gt;clientSecret = env('KEYCLOAK_CLIENT_SECRET');\n    }\n\n    public function checkKeycloakSession()\n    {\n        try {\n            $response = $this-&gt;client-&gt;get($this-&gt;getAuthUrl(), [\n                'query' =&gt; [\n                    'client_id' =&gt; $this-&gt;clientId,\n                    'response_type' =&gt; 'code',\n                    'scope' =&gt; 'openid',\n                    'redirect_uri' =&gt; env('KEYCLOAK_REDIRECT_URI'),\n                    'prompt' =&gt; 'none'\n                ],\n                'allow_redirects' =&gt; false,\n                'cookies' =&gt; request()-&gt;cookies,\n            ]);\n\n            return $response-&gt;getStatusCode() === 302 &amp;&amp; \n                   strpos($response-&gt;getHeader('Location')[0] ?? '', 'code=') !== false;\n                   \n        } catch (\\Exception $e) {\n            Log::error('SSO Session Check Error: ' . $e-&gt;getMessage());\n            return false;\n        }\n    }\n\n    public function refreshToken($refreshToken)\n    {\n        try {\n            $response = $this-&gt;client-&gt;post($this-&gt;getTokenUrl(), [\n                'form_params' =&gt; [\n                    'grant_type' =&gt; 'refresh_token',\n                    'client_id' =&gt; $this-&gt;clientId,\n                    'client_secret' =&gt; $this-&gt;clientSecret,\n                    'refresh_token' =&gt; $refreshToken,\n                ]\n            ]);\n\n            return json_decode($response-&gt;getBody(), true);\n        } catch (\\Exception $e) {\n            Log::error('Token Refresh Error: ' . $e-&gt;getMessage());\n            return false;\n        }\n    }\n\n    public function extractRolesFromToken($token)\n    {\n        try {\n            $tokenParts = explode('.', $token);\n            $payload = json_decode(base64_decode($tokenParts[1]), true);\n            \n            return $payload['realm_access']['roles'] ?? [];\n        } catch (\\Exception $e) {\n            Log::error('Token Role Extraction Error: ' . $e-&gt;getMessage());\n            return [];\n        }\n    }\n\n    private function getAuthUrl()\n    {\n        return $this-&gt;keycloakBaseUrl . '\/realms\/' . $this-&gt;realm . '\/protocol\/openid-connect\/auth';\n    }\n\n    private function getTokenUrl()\n    {\n        return $this-&gt;keycloakBaseUrl . '\/realms\/' . $this-&gt;realm . '\/protocol\/openid-connect\/token';\n    }\n\n    public function getLogoutUrl($postLogoutRedirectUri = null)\n    {\n        $logoutUrl = $this-&gt;keycloakBaseUrl . '\/realms\/' . $this-&gt;realm . '\/protocol\/openid-connect\/logout';\n        \n        if ($postLogoutRedirectUri) {\n            $logoutUrl .= '?post_logout_redirect_uri=' . urlencode($postLogoutRedirectUri);\n        }\n        \n        return $logoutUrl;\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-5-keycloak-controllers-create-for-each-app\">Part 5: Keycloak Controllers (Create for Each App)<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5.1: Standard Laravel Apps Controller<\/h2>\n\n\n\n<p>Create <code>app\/Http\/Controllers\/Auth\/KeycloakController.php<\/code> for regular Laravel apps:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ app\/Http\/Controllers\/Auth\/KeycloakController.php<\/em>\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Http\\Controllers\\Controller;\nuse App\\Models\\User;\nuse App\\Services\\SSOService;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Support\\Str;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse Illuminate\\Http\\Request;\n\nclass KeycloakController extends Controller\n{\n    protected $ssoService;\n\n    public function __construct(SSOService $ssoService)\n    {\n        $this-&gt;ssoService = $ssoService;\n    }\n\n    public function redirect()\n    {\n        return Socialite::driver('keycloak')-&gt;redirect();\n    }\n\n    public function callback()\n    {\n        try {\n            $keycloakUser = Socialite::driver('keycloak')-&gt;user();\n            \n            <em>\/\/ Extract roles from token<\/em>\n            $roles = $this-&gt;ssoService-&gt;extractRolesFromToken($keycloakUser-&gt;token);\n            \n            <em>\/\/ Create or update user<\/em>\n            $user = User::updateOrCreate(\n                ['email' =&gt; $keycloakUser-&gt;getEmail()],\n                [\n                    'name' =&gt; $keycloakUser-&gt;getName(),\n                    'email' =&gt; $keycloakUser-&gt;getEmail(),\n                    'keycloak_id' =&gt; $keycloakUser-&gt;getId(),\n                    'keycloak_roles' =&gt; json_encode($roles),\n                    'password' =&gt; Hash::make(Str::random(24)),\n                    'email_verified_at' =&gt; now(),\n                    'last_keycloak_sync' =&gt; now(),\n                ]\n            );\n\n            Auth::login($user);\n\n            <em>\/\/ Store SSO session data<\/em>\n            session([\n                'keycloak_token' =&gt; $keycloakUser-&gt;token,\n                'keycloak_refresh_token' =&gt; $keycloakUser-&gt;refreshToken,\n                'sso_authenticated' =&gt; true,\n                'user_roles' =&gt; $roles\n            ]);\n\n            return redirect()-&gt;intended('\/dashboard');\n            \n        } catch (\\Exception $e) {\n            \\Log::error('Keycloak Auth Error: ' . $e-&gt;getMessage());\n            return redirect('\/login')-&gt;with('error', 'Authentication failed: ' . $e-&gt;getMessage());\n        }\n    }\n\n    public function logout(Request $request)\n    {\n        $postLogoutUri = url('\/');\n        \n        Auth::logout();\n        $request-&gt;session()-&gt;invalidate();\n        $request-&gt;session()-&gt;regenerateToken();\n        \n        return redirect($this-&gt;ssoService-&gt;getLogoutUrl($postLogoutUri));\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5.2: Eventmie Pro Controller<\/h2>\n\n\n\n<p>Create <code>app\/Http\/Controllers\/Auth\/EventmieKeycloakController.php<\/code> for Eventmie:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ app\/Http\/Controllers\/Auth\/EventmieKeycloakController.php<\/em>\n\nnamespace App\\Http\\Controllers\\Auth;\n\nuse App\\Http\\Controllers\\Controller;\nuse App\\Services\\SSOService;\nuse Illuminate\\Support\\Facades\\Auth;\nuse Illuminate\\Support\\Facades\\Hash;\nuse Illuminate\\Support\\Str;\nuse Laravel\\Socialite\\Facades\\Socialite;\nuse Illuminate\\Http\\Request;\n\n<em>\/\/ Use Eventmie's User model<\/em>\nuse Classiebit\\Eventmie\\Models\\User;\n\nclass EventmieKeycloakController extends Controller\n{\n    protected $ssoService;\n\n    public function __construct(SSOService $ssoService)\n    {\n        $this-&gt;ssoService = $ssoService;\n    }\n\n    public function redirect()\n    {\n        return Socialite::driver('keycloak')-&gt;redirect();\n    }\n\n    public function adminRedirect()\n    {\n        return Socialite::driver('keycloak')\n            -&gt;with(['redirect_uri' =&gt; env('KEYCLOAK_ADMIN_REDIRECT_URI')])\n            -&gt;redirect();\n    }\n\n    public function callback()\n    {\n        try {\n            $keycloakUser = Socialite::driver('keycloak')-&gt;user();\n            $roles = $this-&gt;ssoService-&gt;extractRolesFromToken($keycloakUser-&gt;token);\n            \n            <em>\/\/ Create or update Eventmie user<\/em>\n            $user = User::updateOrCreate(\n                ['email' =&gt; $keycloakUser-&gt;getEmail()],\n                [\n                    'first_name' =&gt; $this-&gt;getFirstName($keycloakUser-&gt;getName()),\n                    'last_name' =&gt; $this-&gt;getLastName($keycloakUser-&gt;getName()),\n                    'email' =&gt; $keycloakUser-&gt;getEmail(),\n                    'keycloak_id' =&gt; $keycloakUser-&gt;getId(),\n                    'keycloak_roles' =&gt; json_encode($roles),\n                    'password' =&gt; Hash::make(Str::random(24)),\n                    'email_verified_at' =&gt; now(),\n                    'last_keycloak_sync' =&gt; now(),\n                    'is_organiser' =&gt; in_array('eventmie_organizer', $roles) ? 1 : 0,\n                    'is_admin' =&gt; in_array('eventmie_admin', $roles) || in_array('app_admin', $roles) ? 1 : 0,\n                ]\n            );\n\n            Auth::login($user);\n\n            <em>\/\/ Store SSO session data<\/em>\n            session([\n                'keycloak_token' =&gt; $keycloakUser-&gt;token,\n                'keycloak_refresh_token' =&gt; $keycloakUser-&gt;refreshToken,\n                'sso_authenticated' =&gt; true,\n                'user_roles' =&gt; $roles\n            ]);\n\n            return $this-&gt;redirectBasedOnRole($user);\n            \n        } catch (\\Exception $e) {\n            \\Log::error('Eventmie Keycloak Auth Error: ' . $e-&gt;getMessage());\n            return redirect('\/login')-&gt;with('error', 'Authentication failed');\n        }\n    }\n\n    public function adminCallback()\n    {\n        try {\n            $keycloakUser = Socialite::driver('keycloak')\n                -&gt;with(['redirect_uri' =&gt; env('KEYCLOAK_ADMIN_REDIRECT_URI')])\n                -&gt;user();\n            \n            $roles = $this-&gt;ssoService-&gt;extractRolesFromToken($keycloakUser-&gt;token);\n            \n            <em>\/\/ Check admin privileges<\/em>\n            if (!in_array('eventmie_admin', $roles) &amp;&amp; !in_array('app_admin', $roles)) {\n                return redirect('\/')-&gt;with('error', 'Insufficient admin privileges');\n            }\n\n            $user = User::updateOrCreate(\n                ['email' =&gt; $keycloakUser-&gt;getEmail()],\n                [\n                    'first_name' =&gt; $this-&gt;getFirstName($keycloakUser-&gt;getName()),\n                    'last_name' =&gt; $this-&gt;getLastName($keycloakUser-&gt;getName()),\n                    'email' =&gt; $keycloakUser-&gt;getEmail(),\n                    'keycloak_id' =&gt; $keycloakUser-&gt;getId(),\n                    'keycloak_roles' =&gt; json_encode($roles),\n                    'password' =&gt; Hash::make(Str::random(24)),\n                    'email_verified_at' =&gt; now(),\n                    'is_admin' =&gt; 1,\n                    'last_keycloak_sync' =&gt; now(),\n                ]\n            );\n\n            Auth::login($user);\n            session([\n                'keycloak_token' =&gt; $keycloakUser-&gt;token,\n                'keycloak_refresh_token' =&gt; $keycloakUser-&gt;refreshToken,\n                'sso_authenticated' =&gt; true,\n                'user_roles' =&gt; $roles\n            ]);\n\n            return redirect('\/admin\/dashboard');\n            \n        } catch (\\Exception $e) {\n            return redirect('\/admin\/login')-&gt;with('error', 'Admin authentication failed');\n        }\n    }\n\n    private function getFirstName($fullName)\n    {\n        $names = explode(' ', $fullName);\n        return $names[0] ?? '';\n    }\n\n    private function getLastName($fullName)\n    {\n        $names = explode(' ', $fullName);\n        return isset($names[1]) ? implode(' ', array_slice($names, 1)) : '';\n    }\n\n    private function redirectBasedOnRole($user)\n    {\n        if ($user-&gt;is_admin) {\n            return redirect('\/admin\/dashboard');\n        } elseif ($user-&gt;is_organiser) {\n            return redirect('\/organiser\/dashboard');\n        } else {\n            return redirect('\/');\n        }\n    }\n\n    public function logout(Request $request)\n    {\n        $postLogoutUri = url('\/');\n        \n        Auth::logout();\n        $request-&gt;session()-&gt;invalidate();\n        $request-&gt;session()-&gt;regenerateToken();\n        \n        return redirect($this-&gt;ssoService-&gt;getLogoutUrl($postLogoutUri));\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-6-sso-middleware-create-for-all-apps\">Part 6: SSO Middleware (Create for All Apps)<\/h2>\n\n\n\n<p>Create <code>app\/Http\/Middleware\/SSOMiddleware.php<\/code> in ALL applications:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ app\/Http\/Middleware\/SSOMiddleware.php<\/em>\n\nnamespace App\\Http\\Middleware;\n\nuse App\\Services\\SSOService;\nuse Closure;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\Auth;\n\nclass SSOMiddleware\n{\n    protected $ssoService;\n\n    public function __construct(SSOService $ssoService)\n    {\n        $this-&gt;ssoService = $ssoService;\n    }\n\n    public function handle(Request $request, Closure $next)\n    {\n        <em>\/\/ If user is already authenticated, continue<\/em>\n        if (Auth::check()) {\n            <em>\/\/ Check if token needs refresh<\/em>\n            $this-&gt;refreshTokenIfNeeded();\n            return $next($request);\n        }\n\n        <em>\/\/ Check for existing SSO session<\/em>\n        if ($this-&gt;hasActiveSSO()) {\n            <em>\/\/ Redirect to SSO login for silent authentication<\/em>\n            return redirect()-&gt;route('keycloak.login');\n        }\n\n        return $next($request);\n    }\n\n    private function hasActiveSSO()\n    {\n        <em>\/\/ Check if we have SSO session indicators<\/em>\n        if (session('sso_authenticated')) {\n            return true;\n        }\n\n        <em>\/\/ Check for active Keycloak session<\/em>\n        return $this-&gt;ssoService-&gt;checkKeycloakSession();\n    }\n\n    private function refreshTokenIfNeeded()\n    {\n        $refreshToken = session('keycloak_refresh_token');\n        \n        if ($refreshToken &amp;&amp; $this-&gt;tokenNeedsRefresh()) {\n            $newTokens = $this-&gt;ssoService-&gt;refreshToken($refreshToken);\n            \n            if ($newTokens) {\n                session([\n                    'keycloak_token' =&gt; $newTokens['access_token'],\n                    'keycloak_refresh_token' =&gt; $newTokens['refresh_token'] ?? $refreshToken\n                ]);\n            }\n        }\n    }\n\n    private function tokenNeedsRefresh()\n    {\n        $token = session('keycloak_token');\n        if (!$token) return false;\n\n        try {\n            $tokenParts = explode('.', $token);\n            $payload = json_decode(base64_decode($tokenParts[1]), true);\n            $exp = $payload['exp'] ?? 0;\n            \n            <em>\/\/ Refresh if token expires in next 5 minutes<\/em>\n            return ($exp - time()) &lt; 300;\n        } catch (\\Exception $e) {\n            return true; <em>\/\/ Refresh on any token parsing error<\/em>\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Register middleware in <code>app\/Http\/Kernel.php<\/code> for ALL apps:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>protected $routeMiddleware = [\n    <em>\/\/ ... existing middleware<\/em>\n    'sso' =&gt; \\App\\Http\\Middleware\\SSOMiddleware::class,\n];\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-7-routes-configuration\">Part 7: Routes Configuration<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7.1: Standard Laravel Apps Routes<\/h2>\n\n\n\n<p>Add to <code>routes\/web.php<\/code> in regular Laravel apps:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ routes\/web.php<\/em>\n\nuse App\\Http\\Controllers\\Auth\\KeycloakController;\n\n<em>\/\/ SSO Authentication Routes<\/em>\nRoute::get('\/auth\/keycloak\/redirect', [KeycloakController::class, 'redirect'])\n    -&gt;name('keycloak.login');\nRoute::get('\/auth\/keycloak\/callback', [KeycloakController::class, 'callback'])\n    -&gt;name('keycloak.callback');\nRoute::post('\/auth\/keycloak\/logout', [KeycloakController::class, 'logout'])\n    -&gt;name('keycloak.logout');\n\n<em>\/\/ Protected routes with SSO<\/em>\nRoute::middleware(['sso'])-&gt;group(function () {\n    Route::get('\/dashboard', function () {\n        return view('dashboard');\n    })-&gt;name('dashboard');\n    \n    Route::get('\/profile', function () {\n        return view('profile');\n    })-&gt;name('profile');\n    \n    <em>\/\/ Add your other protected routes here<\/em>\n});\n\n<em>\/\/ Home page (accessible to everyone)<\/em>\nRoute::get('\/', function () {\n    return view('welcome');\n});\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7.2: Eventmie Pro Routes<\/h2>\n\n\n\n<p>Add to <code>routes\/web.php<\/code> in Eventmie:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">php<code>&lt;?php\n<em>\/\/ routes\/web.php<\/em>\n\nuse App\\Http\\Controllers\\Auth\\EventmieKeycloakController;\n\n<em>\/\/ Eventmie SSO Routes<\/em>\nRoute::get('\/auth\/keycloak\/redirect', [EventmieKeycloakController::class, 'redirect'])\n    -&gt;name('keycloak.login');\nRoute::get('\/auth\/keycloak\/callback', [EventmieKeycloakController::class, 'callback'])\n    -&gt;name('keycloak.callback');\n\n<em>\/\/ Admin SSO Routes<\/em>\nRoute::get('\/admin\/auth\/keycloak\/redirect', [EventmieKeycloakController::class, 'adminRedirect'])\n    -&gt;name('admin.keycloak.login');\nRoute::get('\/admin\/auth\/keycloak\/callback', [EventmieKeycloakController::class, 'adminCallback'])\n    -&gt;name('admin.keycloak.callback');\n\n<em>\/\/ Logout<\/em>\nRoute::post('\/auth\/keycloak\/logout', [EventmieKeycloakController::class, 'logout'])\n    -&gt;name('keycloak.logout');\n\n<em>\/\/ Apply SSO middleware to Eventmie routes<\/em>\nRoute::middleware(['sso'])-&gt;group(function () {\n    <em>\/\/ User dashboard<\/em>\n    Route::get('\/dashboard', 'EventmieController@dashboard')-&gt;name('dashboard');\n    \n    <em>\/\/ Organiser routes<\/em>\n    Route::prefix('organiser')-&gt;middleware(['auth', 'organiser'])-&gt;group(function () {\n        <em>\/\/ Eventmie organiser routes<\/em>\n    });\n    \n    <em>\/\/ Booking routes<\/em>\n    Route::prefix('bookings')-&gt;middleware(['auth'])-&gt;group(function () {\n        <em>\/\/ Eventmie booking routes<\/em>\n    });\n});\n\n<em>\/\/ Admin routes<\/em>\nRoute::middleware(['sso', 'auth', 'admin'])-&gt;prefix('admin')-&gt;group(function () {\n    Route::get('\/dashboard', 'AdminController@dashboard');\n    <em>\/\/ Other admin routes<\/em>\n});\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-8-frontend-integration\">Part 8: Frontend Integration<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 8.1: Standard Laravel Login View<\/h2>\n\n\n\n<p>Update <code>resources\/views\/auth\/login.blade.php<\/code> in regular Laravel apps:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>@extends('layouts.app')\n\n@section('content')\n&lt;div class=\"container\"&gt;\n    &lt;div class=\"row justify-content-center\"&gt;\n        &lt;div class=\"col-md-8\"&gt;\n            &lt;div class=\"card\"&gt;\n                &lt;div class=\"card-header\"&gt;{{ __('Login') }}&lt;\/div&gt;\n\n                &lt;div class=\"card-body\"&gt;\n                    {{-- Regular Login Form --}}\n                    &lt;form method=\"POST\" action=\"{{ route('login') }}\"&gt;\n                        @csrf\n\n                        &lt;div class=\"row mb-3\"&gt;\n                            &lt;label for=\"email\" class=\"col-md-4 col-form-label text-md-end\"&gt;{{ __('Email Address') }}&lt;\/label&gt;\n                            &lt;div class=\"col-md-6\"&gt;\n                                &lt;input id=\"email\" type=\"email\" class=\"form-control @error('email') is-invalid @enderror\" name=\"email\" value=\"{{ old('email') }}\" required autocomplete=\"email\" autofocus&gt;\n                                @error('email')\n                                    &lt;span class=\"invalid-feedback\" role=\"alert\"&gt;\n                                        &lt;strong&gt;{{ $message }}&lt;\/strong&gt;\n                                    &lt;\/span&gt;\n                                @enderror\n                            &lt;\/div&gt;\n                        &lt;\/div&gt;\n\n                        &lt;div class=\"row mb-3\"&gt;\n                            &lt;label for=\"password\" class=\"col-md-4 col-form-label text-md-end\"&gt;{{ __('Password') }}&lt;\/label&gt;\n                            &lt;div class=\"col-md-6\"&gt;\n                                &lt;input id=\"password\" type=\"password\" class=\"form-control @error('password') is-invalid @enderror\" name=\"password\" required autocomplete=\"current-password\"&gt;\n                                @error('password')\n                                    &lt;span class=\"invalid-feedback\" role=\"alert\"&gt;\n                                        &lt;strong&gt;{{ $message }}&lt;\/strong&gt;\n                                    &lt;\/span&gt;\n                                @enderror\n                            &lt;\/div&gt;\n                        &lt;\/div&gt;\n\n                        &lt;div class=\"row mb-0\"&gt;\n                            &lt;div class=\"col-md-8 offset-md-4\"&gt;\n                                &lt;button type=\"submit\" class=\"btn btn-primary\"&gt;\n                                    {{ __('Login') }}\n                                &lt;\/button&gt;\n                            &lt;\/div&gt;\n                        &lt;\/div&gt;\n                    &lt;\/form&gt;\n\n                    &lt;hr class=\"my-4\"&gt;\n\n                    {{-- SSO Login Button --}}\n                    &lt;div class=\"text-center\"&gt;\n                        &lt;h5 class=\"mb-3\"&gt;Or&lt;\/h5&gt;\n                        &lt;a href=\"{{ route('keycloak.login') }}\" class=\"btn btn-success btn-lg btn-block\"&gt;\n                            &lt;i class=\"fas fa-sign-in-alt\"&gt;&lt;\/i&gt; \n                            Sign in with SSO\n                        &lt;\/a&gt;\n                        &lt;p class=\"text-muted mt-2\"&gt;\n                            &lt;small&gt;Login once, access all applications&lt;\/small&gt;\n                        &lt;\/p&gt;\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/div&gt;\n@endsection\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 8.2: Eventmie Login View<\/h2>\n\n\n\n<p>Update Eventmie&#8217;s login view (usually in <code>resources\/views\/eventmie\/auth\/login.blade.php<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>@extends('eventmie::layouts.app')\n\n@section('content')\n&lt;div class=\"lgx-page-wrapper\"&gt;\n    &lt;section&gt;\n        &lt;div class=\"container\"&gt;\n            &lt;div class=\"row\"&gt;\n                &lt;div class=\"col-xs-12 col-sm-12 col-md-12 col-lg-12\"&gt;\n                    &lt;div class=\"lgx-login-form-box\"&gt;\n                        &lt;div class=\"lgx-login-form-title\"&gt;\n                            &lt;h3&gt;{{ __('eventmie-pro::em.login') }}&lt;\/h3&gt;\n                        &lt;\/div&gt;\n                        \n                        {{-- Regular Eventmie Login Form --}}\n                        &lt;form method=\"POST\" action=\"{{ eventmie_url().'\/login' }}\"&gt;\n                            @csrf\n                            {{-- Your existing Eventmie form fields --}}\n                            &lt;div class=\"form-group\"&gt;\n                                &lt;input type=\"email\" name=\"email\" class=\"form-control\" placeholder=\"{{ __('eventmie-pro::em.email') }}\" required&gt;\n                            &lt;\/div&gt;\n                            \n                            &lt;div class=\"form-group\"&gt;\n                                &lt;input type=\"password\" name=\"password\" class=\"form-control\" placeholder=\"{{ __('eventmie-pro::em.password') }}\" required&gt;\n                            &lt;\/div&gt;\n                            \n                            &lt;button type=\"submit\" class=\"btn btn-primary btn-block\"&gt;\n                                {{ __('eventmie-pro::em.login') }}\n                            &lt;\/button&gt;\n                        &lt;\/form&gt;\n\n                        {{-- SSO Section --}}\n                        &lt;div class=\"sso-divider my-4\"&gt;\n                            &lt;div class=\"text-center\"&gt;\n                                &lt;span class=\"divider-text bg-white px-3 text-muted\"&gt;OR&lt;\/span&gt;\n                            &lt;\/div&gt;\n                        &lt;\/div&gt;\n\n                        &lt;div class=\"sso-login-section\"&gt;\n                            &lt;a href=\"{{ route('keycloak.login') }}\" class=\"btn btn-success btn-lg btn-block mb-3\"&gt;\n                                &lt;i class=\"fas fa-key\"&gt;&lt;\/i&gt;\n                                {{ __('Sign in with SSO') }}\n                            &lt;\/a&gt;\n                            \n                            @if(request()-&gt;is('admin*'))\n                            &lt;a href=\"{{ route('admin.keycloak.login') }}\" class=\"btn btn-info btn-lg btn-block\"&gt;\n                                &lt;i class=\"fas fa-shield-alt\"&gt;&lt;\/i&gt;\n                                {{ __('Admin SSO Login') }}\n                            &lt;\/a&gt;\n                            @endif\n                        &lt;\/div&gt;\n                    &lt;\/div&gt;\n                &lt;\/div&gt;\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    &lt;\/section&gt;\n&lt;\/div&gt;\n\n&lt;style&gt;\n.sso-divider {\n    position: relative;\n}\n.sso-divider::before {\n    content: '';\n    position: absolute;\n    top: 50%;\n    left: 0;\n    right: 0;\n    height: 1px;\n    background: #ddd;\n}\n.divider-text {\n    position: relative;\n    z-index: 1;\n}\n&lt;\/style&gt;\n@endsection\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 8.3: Navigation Bar Updates (All Apps)<\/h2>\n\n\n\n<p>Update navigation to include SSO logout:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>{{-- For regular Laravel apps --}}\n&lt;nav class=\"navbar navbar-expand-md navbar-light bg-white shadow-sm\"&gt;\n    &lt;div class=\"container\"&gt;\n        {{-- Navigation items --}}\n        \n        &lt;div class=\"navbar-nav ms-auto\"&gt;\n            @guest\n                &lt;a class=\"nav-link\" href=\"{{ route('login') }}\"&gt;{{ __('Login') }}&lt;\/a&gt;\n                &lt;a class=\"nav-link\" href=\"{{ route('keycloak.login') }}\"&gt;{{ __('SSO Login') }}&lt;\/a&gt;\n            @else\n                &lt;li class=\"nav-item dropdown\"&gt;\n                    &lt;a class=\"nav-link dropdown-toggle\" href=\"#\" role=\"button\" data-bs-toggle=\"dropdown\"&gt;\n                        {{ Auth::user()-&gt;name }}\n                    &lt;\/a&gt;\n                    &lt;ul class=\"dropdown-menu\"&gt;\n                        &lt;li&gt;\n                            &lt;form method=\"POST\" action=\"{{ route('keycloak.logout') }}\"&gt;\n                                @csrf\n                                &lt;button type=\"submit\" class=\"dropdown-item\"&gt;\n                                    {{ __('Logout') }}\n                                &lt;\/button&gt;\n                            &lt;\/form&gt;\n                        &lt;\/li&gt;\n                    &lt;\/ul&gt;\n                &lt;\/li&gt;\n            @endguest\n        &lt;\/div&gt;\n    &lt;\/div&gt;\n&lt;\/nav&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-9-javascript-for-seamless-sso\">Part 9: JavaScript for Seamless SSO<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 9.1: SSO Detection Script<\/h2>\n\n\n\n<p>Create <code>public\/js\/sso-detector.js<\/code> for ALL applications:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">javascript<code><em>\/\/ public\/js\/sso-detector.js<\/em>\n\nclass SSODetector {\n    constructor() {\n        this.checkInterval = 30000; <em>\/\/ Check every 30 seconds<\/em>\n        this.keycloakBaseUrl = document.querySelector('meta[name=\"keycloak-base-url\"]')?.content;\n        this.keycloakRealm = document.querySelector('meta[name=\"keycloak-realm\"]')?.content;\n        this.init();\n    }\n\n    init() {\n        <em>\/\/ Check SSO status on page load<\/em>\n        this.checkSSOStatus();\n        \n        <em>\/\/ Set up periodic checks<\/em>\n        this.startPeriodicCheck();\n        \n        <em>\/\/ Listen for storage events (cross-tab communication)<\/em>\n        window.addEventListener('storage', (e) =&gt; {\n            if (e.key === 'sso_logout') {\n                this.handleCrossTabLogout();\n            }\n        });\n    }\n\n    async checkSSOStatus() {\n        try {\n            <em>\/\/ Check if user is logged in locally<\/em>\n            const isLoggedIn = document.querySelector('meta[name=\"user-authenticated\"]')?.content === 'true';\n            \n            if (!isLoggedIn) {\n                <em>\/\/ Check if SSO session exists<\/em>\n                const hasSSOSession = await this.checkKeycloakSession();\n                \n                if (hasSSOSession) {\n                    <em>\/\/ Redirect to SSO login<\/em>\n                    window.location.href = '\/auth\/keycloak\/redirect';\n                }\n            }\n        } catch (error) {\n            console.log('SSO check error:', error);\n        }\n    }\n\n    async checkKeycloakSession() {\n        if (!this.keycloakBaseUrl || !this.keycloakRealm) {\n            return false;\n        }\n\n        try {\n            <em>\/\/ Create invisible iframe to check Keycloak session<\/em>\n            const iframe = document.createElement('iframe');\n            iframe.style.display = 'none';\n            iframe.src = `${this.keycloakBaseUrl}\/realms\/${this.keycloakRealm}\/protocol\/openid-connect\/login-status-iframe.html`;\n            \n            document.body.appendChild(iframe);\n            \n            return new Promise((resolve) =&gt; {\n                iframe.onload = () =&gt; {\n                    iframe.contentWindow.postMessage(\n                        'checkSessionState',\n                        `${this.keycloakBaseUrl}`\n                    );\n                };\n                \n                window.addEventListener('message', function handler(event) {\n                    if (event.origin !== iframe.contentWindow.location.origin) {\n                        return;\n                    }\n                    \n                    window.removeEventListener('message', handler);\n                    document.body.removeChild(iframe);\n                    \n                    resolve(event.data === 'authenticated');\n                });\n                \n                <em>\/\/ Timeout after 5 seconds<\/em>\n                setTimeout(() =&gt; {\n                    window.removeEventListener('message', () =&gt; {});\n                    if (document.body.contains(iframe)) {\n                        document.body.removeChild(iframe);\n                    }\n                    resolve(false);\n                }, 5000);\n            });\n        } catch (error) {\n            return false;\n        }\n    }\n\n    startPeriodicCheck() {\n        setInterval(() =&gt; {\n            this.checkSSOStatus();\n        }, this.checkInterval);\n    }\n\n    handleCrossTabLogout() {\n        <em>\/\/ If logout happened in another tab, redirect to login<\/em>\n        window.location.href = '\/login';\n    }\n\n    <em>\/\/ Call this when user logs out<\/em>\n    notifyLogout() {\n        localStorage.setItem('sso_logout', Date.now());\n        localStorage.removeItem('sso_logout'); <em>\/\/ Clean up immediately<\/em>\n    }\n}\n\n<em>\/\/ Initialize SSO detector when DOM is ready<\/em>\ndocument.addEventListener('DOMContentLoaded', () =&gt; {\n    new SSODetector();\n});\n\n<em>\/\/ Global logout function<\/em>\nwindow.ssoLogout = function() {\n    <em>\/\/ Notify other tabs<\/em>\n    if (window.SSODetector) {\n        window.SSODetector.notifyLogout();\n    }\n    \n    <em>\/\/ Perform logout<\/em>\n    const form = document.createElement('form');\n    form.method = 'POST';\n    form.action = '\/auth\/keycloak\/logout';\n    \n    const csrfToken = document.querySelector('meta[name=\"csrf-token\"]')?.content;\n    if (csrfToken) {\n        const csrfInput = document.createElement('input');\n        csrfInput.type = 'hidden';\n        csrfInput.name = '_token';\n        csrfInput.value = csrfToken;\n        form.appendChild(csrfInput);\n    }\n    \n    document.body.appendChild(form);\n    form.submit();\n};\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 9.2: Include Script in Layouts<\/h2>\n\n\n\n<p>Add meta tags and script to your main layout (ALL apps):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>{{-- resources\/views\/layouts\/app.blade.php --}}\n&lt;!doctype html&gt;\n&lt;html lang=\"{{ str_replace('_', '-', app()-&gt;getLocale()) }}\"&gt;\n&lt;head&gt;\n    &lt;meta charset=\"utf-8\"&gt;\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"&gt;\n    &lt;meta name=\"csrf-token\" content=\"{{ csrf_token() }}\"&gt;\n    \n    {{-- SSO Meta Tags --}}\n    &lt;meta name=\"keycloak-base-url\" content=\"{{ env('KEYCLOAK_BASE_URL') }}\"&gt;\n    &lt;meta name=\"keycloak-realm\" content=\"{{ env('KEYCLOAK_REALM') }}\"&gt;\n    &lt;meta name=\"user-authenticated\" content=\"{{ auth()-&gt;check() ? 'true' : 'false' }}\"&gt;\n    \n    &lt;title&gt;{{ config('app.name', 'Laravel') }}&lt;\/title&gt;\n    \n    {{-- Styles --}}\n    @vite(['resources\/sass\/app.scss', 'resources\/js\/app.js'])\n&lt;\/head&gt;\n&lt;body&gt;\n    &lt;div id=\"app\"&gt;\n        {{-- Your app content --}}\n        @yield('content')\n    &lt;\/div&gt;\n    \n    {{-- SSO Detection Script --}}\n    &lt;script src=\"{{ asset('js\/sso-detector.js') }}\"&gt;&lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-10-local-development-setup\">Part 10: Local Development Setup<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 10.1: Configure Local Hosts<\/h2>\n\n\n\n<p>Add to your <code>\/etc\/hosts<\/code> file (Windows: <code>C:\\Windows\\System32\\drivers\\etc\\hosts<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>127.0.0.1 laravel-app1.local\n127.0.0.1 laravel-app2.local\n127.0.0.1 eventmie.local\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 10.2: Virtual Host Configuration<\/h2>\n\n\n\n<p><strong>For Apache<\/strong> (<code>\/etc\/apache2\/sites-available\/<\/code>):<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code># laravel-app1.local.conf\n&lt;VirtualHost *:80&gt;\n    ServerName laravel-app1.local\n    DocumentRoot \/path\/to\/laravel-app1\/public\n    &lt;Directory \/path\/to\/laravel-app1\/public&gt;\n        AllowOverride All\n        Require all granted\n    &lt;\/Directory&gt;\n&lt;\/VirtualHost&gt;\n\n# laravel-app2.local.conf\n&lt;VirtualHost *:80&gt;\n    ServerName laravel-app2.local\n    DocumentRoot \/path\/to\/laravel-app2\/public\n    &lt;Directory \/path\/to\/laravel-app2\/public&gt;\n        AllowOverride All\n        Require all granted\n    &lt;\/Directory&gt;\n&lt;\/VirtualHost&gt;\n\n# eventmie.local.conf\n&lt;VirtualHost *:80&gt;\n    ServerName eventmie.local\n    DocumentRoot \/path\/to\/eventmie\/public\n    &lt;Directory \/path\/to\/eventmie\/public&gt;\n        AllowOverride All\n        Require all granted\n    &lt;\/Directory&gt;\n&lt;\/VirtualHost&gt;\n<\/code><\/pre>\n\n\n\n<p>Enable sites:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code>sudo a2ensite laravel-app1.local.conf\nsudo a2ensite laravel-app2.local.conf\nsudo a2ensite eventmie.local.conf\nsudo systemctl reload apache2\n<\/code><\/pre>\n\n\n\n<p><strong>For Nginx<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code># \/etc\/nginx\/sites-available\/laravel-sso-apps\nserver {\n    listen 80;\n    server_name laravel-app1.local;\n    root \/path\/to\/laravel-app1\/public;\n    index index.php index.html;\n    \n    location \/ {\n        try_files $uri $uri\/ \/index.php?$query_string;\n    }\n    \n    location ~ \\.php$ {\n        fastcgi_pass unix:\/var\/run\/php\/php8.1-fpm.sock;\n        fastcgi_index index.php;\n        include fastcgi_params;\n        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n    }\n}\n\nserver {\n    listen 80;\n    server_name laravel-app2.local;\n    root \/path\/to\/laravel-app2\/public;\n    index index.php index.html;\n    \n    location \/ {\n        try_files $uri $uri\/ \/index.php?$query_string;\n    }\n    \n    location ~ \\.php$ {\n        fastcgi_pass unix:\/var\/run\/php\/php8.1-fpm.sock;\n        fastcgi_index index.php;\n        include fastcgi_params;\n        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n    }\n}\n\nserver {\n    listen 80;\n    server_name eventmie.local;\n    root \/path\/to\/eventmie\/public;\n    index index.php index.html;\n    \n    location \/ {\n        try_files $uri $uri\/ \/index.php?$query_string;\n    }\n    \n    location ~ \\.php$ {\n        fastcgi_pass unix:\/var\/run\/php\/php8.1-fpm.sock;\n        fastcgi_index index.php;\n        include fastcgi_params;\n        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-11-testing-the-complete-sso-setup\">Part 11: Testing the Complete SSO Setup<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 11.1: Start All Services<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code><em># Start Keycloak<\/em>\ndocker start keycloak-sso\n\n<em># Verify Keycloak is running<\/em>\ncurl http:\/\/localhost:8080\/realms\/laravel-sso-realm\n\n<em># Start web server (Apache\/Nginx)<\/em>\nsudo systemctl start apache2  <em># or nginx<\/em>\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 11.2: Test SSO Flow<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Initial Test<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Visit <code>http:\/\/laravel-app1.local<\/code><\/li>\n\n\n\n<li>Click &#8220;Sign in with SSO&#8221;<\/li>\n\n\n\n<li>Login with Keycloak credentials<\/li>\n\n\n\n<li>Verify you&#8217;re redirected back to Laravel App 1<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Cross-App SSO Test<\/strong>:\n<ul class=\"wp-block-list\">\n<li>While logged into App 1, visit <code>http:\/\/laravel-app2.local<\/code><\/li>\n\n\n\n<li>You should be automatically logged in (no login prompt)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Eventmie Integration Test<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Visit <code>http:\/\/eventmie.local<\/code><\/li>\n\n\n\n<li>Should automatically log you in<\/li>\n\n\n\n<li>Test organizer\/admin access based on roles<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Logout Test<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Logout from any app<\/li>\n\n\n\n<li>Visit other apps &#8211; should require login again<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Step 11.3: Role-Based Access Test<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Create different test users in Keycloak<\/strong> with different roles<\/li>\n\n\n\n<li><strong>Test each role<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Regular user: Should access basic features<\/li>\n\n\n\n<li>Organizer: Should access Eventmie organizer features<\/li>\n\n\n\n<li>Admin: Should access admin panels<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-12-production-deployment\">Part 12: Production Deployment<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Step 12.1: Environment Variables for Production<\/h2>\n\n\n\n<p>Update <code>.env<\/code> files for production:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code># Use HTTPS URLs\nKEYCLOAK_BASE_URL=https:\/\/keycloak.yourdomain.com\nKEYCLOAK_REDIRECT_URI=\"https:\/\/app1.yourdomain.com\/auth\/keycloak\/callback\"\n\n# Secure session configuration\nSESSION_DOMAIN=.yourdomain.com\nSESSION_SECURE=true\nSESSION_SAME_SITE=none\n\n# Enable Redis for session sharing\nSESSION_DRIVER=redis\nREDIS_HOST=your-redis-server\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 12.2: SSL Certificate Setup<\/h2>\n\n\n\n<p>Ensure all applications have SSL certificates:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code><em># Using Let's Encrypt with Certbot<\/em>\nsudo certbot --apache -d app1.yourdomain.com\nsudo certbot --apache -d app2.yourdomain.com\nsudo certbot --apache -d eventmie.yourdomain.com\nsudo certbot --apache -d keycloak.yourdomain.com\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 12.3: Keycloak Production Configuration<\/h2>\n\n\n\n<p>Update Keycloak redirect URIs for production:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">text<code>https:\/\/app1.yourdomain.com\/auth\/keycloak\/callback\nhttps:\/\/app2.yourdomain.com\/auth\/keycloak\/callback\nhttps:\/\/eventmie.yourdomain.com\/auth\/keycloak\/callback\nhttps:\/\/eventmie.yourdomain.com\/admin\/auth\/keycloak\/callback\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-13-troubleshooting-guide\">Part 13: Troubleshooting Guide<\/h2>\n\n\n\n<h2 class=\"wp-block-heading\">Common Issues and Solutions<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>&#8220;Invalid redirect URI&#8221; error<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Check Keycloak client configuration<\/li>\n\n\n\n<li>Ensure all redirect URIs are added<\/li>\n\n\n\n<li>Verify correct protocol (http\/https)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Sessions not shared across apps<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Verify <code>SESSION_DOMAIN<\/code> is set to root domain<\/li>\n\n\n\n<li>Check that all apps use same session driver<\/li>\n\n\n\n<li>Ensure Redis\/database is accessible by all apps<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>User not automatically logged in<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Check SSO middleware is applied to routes<\/li>\n\n\n\n<li>Verify JavaScript SSO detector is loaded<\/li>\n\n\n\n<li>Check browser console for errors<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Role mapping issues<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Verify roles are created in Keycloak<\/li>\n\n\n\n<li>Check token parsing in SSOService<\/li>\n\n\n\n<li>Ensure user model updates roles correctly<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Keycloak connection issues<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Verify Keycloak is running and accessible<\/li>\n\n\n\n<li>Check network connectivity<\/li>\n\n\n\n<li>Verify client credentials<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Debug Commands<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\">bash<code><em># Check Keycloak realm configuration<\/em>\ncurl http:\/\/localhost:8080\/realms\/laravel-sso-realm\/.well-known\/openid_configuration\n\n<em># Test token validation<\/em>\nphp artisan tinker\n&gt;&gt;&gt; app(\\App\\Services\\SSOService::class)-&gt;extractRolesFromToken('your-token-here')\n\n<em># Check session data<\/em>\nphp artisan tinker\n&gt;&gt;&gt; session()-&gt;all()\n\n<em># Clear Laravel caches<\/em>\nphp artisan config:clear\nphp artisan cache:clear\nphp artisan route:clear\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"conclusion\">Conclusion<\/h2>\n\n\n\n<p>This complete setup provides seamless Single Sign-On across all your Laravel applications including Eventmie Pro. Users will:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Login once<\/strong> in any application<\/li>\n\n\n\n<li><strong>Automatically be authenticated<\/strong> when visiting other applications<\/li>\n\n\n\n<li><strong>Maintain consistent user roles<\/strong> across all systems<\/li>\n\n\n\n<li><strong>Logout from all applications<\/strong> with a single logout action<\/li>\n<\/ol>\n\n\n\n<p>The setup ensures:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 <strong>Security<\/strong>: All authentication handled by Keycloak<\/li>\n\n\n\n<li>\u2705 <strong>Scalability<\/strong>: Easy to add more Laravel applications<\/li>\n\n\n\n<li>\u2705 <strong>User Experience<\/strong>: Seamless navigation between apps<\/li>\n\n\n\n<li>\u2705 <strong>Role Management<\/strong>: Centralized user role assignment<\/li>\n\n\n\n<li>\u2705 <strong>Session Management<\/strong>: Shared sessions across applications<\/li>\n<\/ul>\n\n\n\n<p>Your users will now have a seamless experience moving between your Laravel applications and Eventmie Pro without needing to log in multiple times.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This comprehensive guide will set up Single Sign-On (SSO) across all your Laravel applications using Keycloak as the Identity Provider. When a user logs into one application,&#8230; <\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2668","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro) - DevOps Consulting<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro) - DevOps Consulting\" \/>\n<meta property=\"og:description\" content=\"This comprehensive guide will set up Single Sign-On (SSO) across all your Laravel applications using Keycloak as the Identity Provider. When a user logs into one application,...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/\" \/>\n<meta property=\"og:site_name\" content=\"DevOps Consulting\" \/>\n<meta property=\"article:published_time\" content=\"2025-08-28T07:12:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2025-08-28T07:12:35+00:00\" \/>\n<meta name=\"author\" content=\"Abhishek Singh\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Abhishek Singh\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\\\/\"},\"author\":{\"name\":\"Abhishek Singh\",\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/#\\\/schema\\\/person\\\/fc397ba8be42f9fdd53450edfc73006f\"},\"headline\":\"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro)\",\"datePublished\":\"2025-08-28T07:12:34+00:00\",\"dateModified\":\"2025-08-28T07:12:35+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\\\/\"},\"wordCount\":861,\"commentCount\":0,\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\\\/\",\"url\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\\\/\",\"name\":\"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro) - DevOps Consulting\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/#website\"},\"datePublished\":\"2025-08-28T07:12:34+00:00\",\"dateModified\":\"2025-08-28T07:12:35+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/#\\\/schema\\\/person\\\/fc397ba8be42f9fdd53450edfc73006f\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\\\/\"]}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/\",\"name\":\"DevOps Consulting\",\"description\":\"DevOps Consulting | SRE Consulting | DevSecOps Consulting | MLOps Consulting\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/#\\\/schema\\\/person\\\/fc397ba8be42f9fdd53450edfc73006f\",\"name\":\"Abhishek Singh\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/790feefe779852cdf344ca7318bf6c13832223c9b3c6bf4d217658412041026d?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/790feefe779852cdf344ca7318bf6c13832223c9b3c6bf4d217658412041026d?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/790feefe779852cdf344ca7318bf6c13832223c9b3c6bf4d217658412041026d?s=96&d=mm&r=g\",\"caption\":\"Abhishek Singh\"},\"description\":\"I\u2019m Abhishek, a DevOps, SRE, DevSecOps, and Cloud expert with a passion for sharing knowledge and real-world experiences. I\u2019ve had the opportunity to work with Cotocus and continue to contribute to multiple platforms where I share insights across different domains: \u2022 DevOps School \u2013 Tech blogs and tutorials \u2022 Holiday Landmark \u2013 Travel stories and guides \u2022 Stocks Mantra \u2013 Stock market strategies and tips \u2022 My Medic Plus \u2013 Health and fitness guidance \u2022 TrueReviewNow \u2013 Honest product reviews \u2022 Wizbrand \u2013 SEO and digital tools for businesses I\u2019m also exploring the fascinating world of Quantum Computing.\",\"url\":\"https:\\\/\\\/www.devopsconsulting.in\\\/blog\\\/author\\\/abhishek\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro) - DevOps Consulting","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/","og_locale":"en_US","og_type":"article","og_title":"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro) - DevOps Consulting","og_description":"This comprehensive guide will set up Single Sign-On (SSO) across all your Laravel applications using Keycloak as the Identity Provider. When a user logs into one application,...","og_url":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/","og_site_name":"DevOps Consulting","article_published_time":"2025-08-28T07:12:34+00:00","article_modified_time":"2025-08-28T07:12:35+00:00","author":"Abhishek Singh","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Abhishek Singh","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/#article","isPartOf":{"@id":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/"},"author":{"name":"Abhishek Singh","@id":"https:\/\/www.devopsconsulting.in\/blog\/#\/schema\/person\/fc397ba8be42f9fdd53450edfc73006f"},"headline":"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro)","datePublished":"2025-08-28T07:12:34+00:00","dateModified":"2025-08-28T07:12:35+00:00","mainEntityOfPage":{"@id":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/"},"wordCount":861,"commentCount":0,"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/","url":"https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/","name":"Complete SSO Setup Guide: Keycloak as IdP for Multiple Laravel Applications (Including Eventmie Pro) - DevOps Consulting","isPartOf":{"@id":"https:\/\/www.devopsconsulting.in\/blog\/#website"},"datePublished":"2025-08-28T07:12:34+00:00","dateModified":"2025-08-28T07:12:35+00:00","author":{"@id":"https:\/\/www.devopsconsulting.in\/blog\/#\/schema\/person\/fc397ba8be42f9fdd53450edfc73006f"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.devopsconsulting.in\/blog\/complete-sso-setup-guide-keycloak-as-idp-for-multiple-laravel-applications-including-eventmie-pro\/"]}]},{"@type":"WebSite","@id":"https:\/\/www.devopsconsulting.in\/blog\/#website","url":"https:\/\/www.devopsconsulting.in\/blog\/","name":"DevOps Consulting","description":"DevOps Consulting | SRE Consulting | DevSecOps Consulting | MLOps Consulting","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.devopsconsulting.in\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.devopsconsulting.in\/blog\/#\/schema\/person\/fc397ba8be42f9fdd53450edfc73006f","name":"Abhishek Singh","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/790feefe779852cdf344ca7318bf6c13832223c9b3c6bf4d217658412041026d?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/790feefe779852cdf344ca7318bf6c13832223c9b3c6bf4d217658412041026d?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/790feefe779852cdf344ca7318bf6c13832223c9b3c6bf4d217658412041026d?s=96&d=mm&r=g","caption":"Abhishek Singh"},"description":"I\u2019m Abhishek, a DevOps, SRE, DevSecOps, and Cloud expert with a passion for sharing knowledge and real-world experiences. I\u2019ve had the opportunity to work with Cotocus and continue to contribute to multiple platforms where I share insights across different domains: \u2022 DevOps School \u2013 Tech blogs and tutorials \u2022 Holiday Landmark \u2013 Travel stories and guides \u2022 Stocks Mantra \u2013 Stock market strategies and tips \u2022 My Medic Plus \u2013 Health and fitness guidance \u2022 TrueReviewNow \u2013 Honest product reviews \u2022 Wizbrand \u2013 SEO and digital tools for businesses I\u2019m also exploring the fascinating world of Quantum Computing.","url":"https:\/\/www.devopsconsulting.in\/blog\/author\/abhishek\/"}]}},"_links":{"self":[{"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/posts\/2668","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/comments?post=2668"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/posts\/2668\/revisions"}],"predecessor-version":[{"id":2669,"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/posts\/2668\/revisions\/2669"}],"wp:attachment":[{"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/media?parent=2668"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/categories?post=2668"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsconsulting.in\/blog\/wp-json\/wp\/v2\/tags?post=2668"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}