Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Keycloak Authenticator Bundle Laravel Package

acsystems/keycloak-authenticator-bundle

Laravel/PHP bundle that integrates Keycloak authentication into your app, providing Keycloak-based login and user session handling for protected routes and APIs with minimal configuration, suitable for SSO-enabled deployments.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation

    composer require acsystems/keycloak-authenticator-bundle
    

    Add to config/bundles.php:

    AcSystems\KeycloakAuthenticatorBundle\KeycloakAuthenticatorBundle::class => ['all' => true],
    
  2. Configuration Publish the default config:

    php artisan vendor:publish --provider="AcSystems\KeycloakAuthenticatorBundle\KeycloakAuthenticatorBundle" --tag="config"
    

    Update config/keycloak.php with your Keycloak realm, client ID, and public key.

  3. First Use Case: JWT Validation In your middleware or controller, inject the KeycloakAuthenticator service:

    use AcSystems\KeycloakAuthenticatorBundle\Service\KeycloakAuthenticator;
    
    public function __construct(private KeycloakAuthenticator $authenticator) {}
    
    public function someProtectedRoute(Request $request) {
        $token = $request->bearerToken();
        $user = $this->authenticator->authenticate($token);
        // $user now contains decoded JWT payload (e.g., username, roles, etc.)
    }
    

Implementation Patterns

Common Workflows

  1. Stateless Authentication Middleware Create a middleware to validate JWTs on protected routes:

    namespace App\Http\Middleware;
    
    use AcSystems\KeycloakAuthenticatorBundle\Service\KeycloakAuthenticator;
    use Closure;
    
    class KeycloakAuthMiddleware {
        public function __construct(private KeycloakAuthenticator $authenticator) {}
    
        public function handle($request, Closure $next) {
            $token = $request->bearerToken();
            if (!$token) {
                return response()->json(['error' => 'Unauthorized'], 401);
            }
    
            $this->authenticator->authenticate($token);
            return $next($request);
        }
    }
    

    Register in app/Http/Kernel.php:

    protected $routeMiddleware = [
        'keycloak.auth' => \App\Http\Middleware\KeycloakAuthMiddleware::class,
    ];
    
  2. Role-Based Access Control (RBAC) Extract roles from the JWT payload and enforce them:

    $user = $this->authenticator->authenticate($token);
    $roles = $user->getRoles(); // e.g., ['admin', 'user']
    
    if (!in_array('admin', $roles)) {
        abort(403, 'Forbidden');
    }
    
  3. Token Refresh Logic Handle short-lived access tokens by validating refresh tokens:

    $refreshToken = $request->input('refresh_token');
    $newAccessToken = $this->authenticator->refreshToken($refreshToken);
    return response()->json(['access_token' => $newAccessToken]);
    
  4. Integration with Laravel Auth Create a custom User model provider or guard:

    // config/auth.php
    'guards' => [
        'keycloak' => [
            'driver' => 'keycloak',
            'provider' => 'keycloak',
        ],
    ],
    

Integration Tips

  1. CORS Configuration Ensure your Keycloak client is configured to accept requests from your Laravel domain. Update config/cors.php to allow Authorization headers:

    'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_headers' => ['*'],
    
  2. Laravel Sanctum/Passport Hybrid Combine with Sanctum for session-based auth while using Keycloak for JWT:

    // Middleware to fallback to Sanctum if no JWT is present
    public function handle($request, Closure $next) {
        if ($request->bearerToken()) {
            $this->authenticator->authenticate($request->bearerToken());
        } else {
            $this->auth->viaRequest($request, 'sanctum');
        }
        return $next($request);
    }
    
  3. Custom Claims Handling Extend the KeycloakAuthenticator to handle custom claims (e.g., sub, email_verified):

    $user = $this->authenticator->authenticate($token);
    $customClaim = $user->getClaim('custom_claim_name');
    
  4. Logging and Monitoring Log authentication events for auditing:

    try {
        $user = $this->authenticator->authenticate($token);
    } catch (\Exception $e) {
        \Log::warning("Keycloak auth failed: {$e->getMessage()}", ['token' => $token]);
        throw $e;
    }
    

Gotchas and Tips

Pitfalls

  1. Public Key Rotation

    • Keycloak rotates public keys periodically. Cache the public key in Laravel’s config or use a service like Keycloak Admin REST API to fetch the latest key dynamically.
    • Fix: Implement a KeycloakKeyFetcher service to refresh keys:
      $publicKey = $this->keyFetcher->fetchPublicKey();
      $this->authenticator->setPublicKey($publicKey);
      
  2. Token Expiration Handling

    • JWTs expire silently. Always check the exp claim manually:
      $user = $this->authenticator->authenticate($token);
      if ($user->getClaim('exp') < time()) {
          abort(401, 'Token expired');
      }
      
  3. Clock Skew Issues

    • Keycloak and Laravel servers might have time synchronization issues, causing exp validation to fail.
    • Fix: Configure a small leeway (e.g., 5 minutes) in the authenticate() method:
      $leeway = 300; // 5 minutes
      $this->authenticator->setLeeway($leeway);
      
  4. Missing kid Header

    • If Keycloak uses multiple signing keys, the JWT must include the kid (key ID) header. The bundle defaults to the first key if kid is missing.
    • Fix: Ensure your Keycloak client is configured to include the kid header or explicitly set it in the bundle:
      $this->authenticator->setKeyId('your_key_id_here');
      
  5. Role Mapping

    • Keycloak roles (e.g., realm_access.roles) may not map directly to Laravel’s role system. Normalize roles in a custom User model:
      public function getRoles() {
          return array_map(function($role) {
              return strtolower(str_replace('.', '_', $role));
          }, $this->getClaim('realm_access.roles', []));
      }
      

Debugging Tips

  1. Enable Verbose Logging Add this to config/keycloak.php:

    'debug' => env('KEYCLOAK_DEBUG', false),
    

    Logs will include JWT payloads and validation steps.

  2. Validate JWT Manually Use an online tool like jwt.io to decode and verify tokens. Compare the output with Laravel’s decoded payload.

  3. Check Keycloak Events Enable Keycloak admin events to audit failed logins:

    # In Keycloak Admin Console:
    Configure "Events" -> "Providers" -> "Admin Events" to log authentication failures.
    
  4. Token Introspection Use Keycloak’s introspection endpoint for debugging:

    $response = Http::withHeaders([
        'Authorization' => 'Bearer ' . $refreshToken,
    ])->post('https://your-keycloak/auth/realms/your-realm/protocol/openid-connect/token/introspect');
    

Extension Points

  1. Custom User Provider Override the default KeycloakUser to integrate with Laravel’s User model:

    namespace App\Providers;
    
    use AcSystems\KeycloakAuthenticatorBundle\Model\KeycloakUser;
    use Illuminate\Contracts\Auth\Authenticatable;
    
    class KeycloakUserProvider extends KeycloakUser implements Authenticatable {
        public function getAuthIdentifier() {
            return $this->getClaim('sub'); // Use Keycloak's subject as Laravel's ID
        }
    
        public function getAuthPassword() {
            return ''; // JWT auth doesn’t use passwords
        }
    }
    
  2. Dynamic Realm/Client Configuration Fetch Keycloak settings from environment variables or a database:

    $this->authenticator->setRealm(env('KEYCLOAK_REALM'));
    $this->authenticator->setClientId(env('KEYCLOAK_CLIENT_ID'));
    
  3. **Web

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings