Weave Code
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

Oauth2 Keycloak Laravel Package

stevenmaguire/oauth2-keycloak

Laravel-friendly OAuth2 client provider for Keycloak using theleague/oauth2-client. Handles Keycloak authorization, token retrieval/refresh, and user profile fetching so your app can authenticate via Keycloak with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

First Steps

  1. Installation

    composer require stevenmaguire/oauth2-keycloak
    

    Ensure league/oauth2-client (v2.x) is also installed (this package extends it).

  2. Basic Setup Register the provider in your Laravel app’s config/auth.php:

    'providers' => [
        'keycloak' => [
            'driver' => 'oauth',
            'model' => App\Models\User::class,
            'provider' => Stevenmaguire\OAuth2\Client\Provider\Keycloak::class,
            'client_id' => env('KEYCLOAK_CLIENT_ID'),
            'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
            'redirect' => env('KEYCLOAK_REDIRECT_URI'),
            'scopes' => ['openid', 'profile', 'email'],
            'config' => [
                'realm' => env('KEYCLOAK_REALM'),
                'domain' => env('KEYCLOAK_DOMAIN'),
                'auth_server_url' => env('KEYCLOAK_AUTH_SERVER_URL'),
            ],
        ],
    ],
    
  3. First Use Case: Login Flow

    • Use Laravel’s built-in OAuth routes (Auth::routes()) or manually trigger:
      $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([
          'clientId' => env('KEYCLOAK_CLIENT_ID'),
          'clientSecret' => env('KEYCLOAK_CLIENT_SECRET'),
          'redirectUri' => env('KEYCLOAK_REDIRECT_URI'),
          'scopes' => ['openid', 'profile'],
          'config' => [
              'realm' => env('KEYCLOAK_REALM'),
              'domain' => env('KEYCLOAK_DOMAIN'),
              'auth_server_url' => env('KEYCLOAK_AUTH_SERVER_URL'),
          ],
      ]);
      
      $authUrl = $provider->getAuthorizationUrl();
      return redirect()->to($authUrl);
      
    • Handle the callback in routes/web.php:
      Route::get('/auth/keycloak/callback', [AuthController::class, 'handleProviderCallback']);
      

Implementation Patterns

Common Workflows

  1. User Authentication

    • Extend Laravel’s Socialite or use the provider directly:
      public function handleProviderCallback()
      {
          $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([...]);
          $token = $provider->getAccessToken('authorization_code', [
              'code' => request('code')
          ]);
          $user = $provider->getResourceOwner($token);
          // Map Keycloak user to your User model.
      }
      
  2. Token Refresh

    • Keycloak uses short-lived access tokens. Refresh them programmatically:
      $newToken = $provider->getAccessToken('refresh_token', [
          'refresh_token' => $storedRefreshToken
      ]);
      
  3. Role-Based Access

    • Fetch user roles from Keycloak’s realm_access claim:
      $userData = $provider->getResourceOwner($token)->toArray();
      $roles = $userData['realm_access']['roles'] ?? [];
      
  4. Custom Claims

    • Extend the User model to hydrate from Keycloak’s userinfo endpoint:
      public function findForPassport($identifier)
      {
          $user = User::where('email', $identifier)->first();
          if (!$user) {
              $provider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([...]);
              $token = $provider->getAccessToken('client_credentials', [...]); // Use client creds for admin tasks
              $userData = $provider->getResourceOwner($token)->toArray();
              $user = User::create([
                  'email' => $userData['email'],
                  'name' => $userData['name'],
                  // Hydrate custom claims (e.g., 'department', 'employee_id')
              ]);
          }
          return $user;
      }
      
  5. Admin Operations (User Management)

    • Use the client_credentials grant to interact with Keycloak’s admin API:
      $adminProvider = new Stevenmaguire\OAuth2\Client\Provider\Keycloak([
          'clientId' => env('KEYCLOAK_ADMIN_CLIENT_ID'),
          'clientSecret' => env('KEYCLOAK_ADMIN_CLIENT_SECRET'),
          'grant_type' => 'client_credentials',
          'scopes' => ['manage-users'],
      ]);
      $token = $adminProvider->getAccessToken('client_credentials', []);
      $adminClient = new \GuzzleHttp\Client();
      $response = $adminClient->get('https://keycloak.example.com/admin/realms/master/users', [
          'headers' => ['Authorization' => 'Bearer ' . $token->getToken()]
      ]);
      

Integration Tips

  • Laravel Socialite Wrapper Create a custom KeycloakProvider to integrate seamlessly with Socialite:

    namespace App\Providers;
    
    use Stevenmaguire\OAuth2\Client\Provider\Keycloak as KeycloakProvider;
    use Laravel\Socialite\Contracts\Factory as SocialiteFactory;
    use Laravel\Socialite\Two\AbstractProvider;
    
    class KeycloakProvider extends AbstractProvider {
        public function getAuthUrl($scopes = [])
        {
            return $this->createKeycloakProvider()->getAuthorizationUrl();
        }
    
        public function getToken($code)
        {
            return $this->createKeycloakProvider()->getAccessToken('authorization_code', ['code' => $code]);
        }
    
        public function getUser()
        {
            return $this->createKeycloakProvider()->getResourceOwner($this->token);
        }
    
        protected function createKeycloakProvider()
        {
            return new KeycloakProvider([
                'clientId' => $this->clientId,
                'clientSecret' => $this->clientSecret,
                'redirectUri' => $this->redirectUrl,
                'scopes' => $this->scopes,
                'config' => [
                    'realm' => config('services.keycloak.realm'),
                    'domain' => config('services.keycloak.domain'),
                    'auth_server_url' => config('services.keycloak.auth_server_url'),
                ],
            ]);
        }
    }
    

    Register it in config/services.php:

    'keycloak' => [
        'client_id' => env('KEYCLOAK_CLIENT_ID'),
        'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
        'redirect' => env('KEYCLOAK_REDIRECT_URI'),
        'realm' => env('KEYCLOAK_REALM'),
        'domain' => env('KEYCLOAK_DOMAIN'),
        'auth_server_url' => env('KEYCLOAK_AUTH_SERVER_URL'),
    ],
    
  • Passport Integration Use Keycloak as an OAuth2 provider for Laravel Passport:

    use Stevenmaguire\OAuth2\Client\Provider\Keycloak;
    use Laravel\Passport\Bridge\PersonalAccessClient;
    
    $provider = new Keycloak([...]);
    $token = $provider->getAccessToken('password', [
        'username' => 'user@example.com',
        'password' => 'password',
        'scope' => 'profile',
    ]);
    $user = $provider->getResourceOwner($token);
    
  • Middleware for Role Checks Create middleware to validate Keycloak roles:

    public function handle($request, Closure $next)
    {
        $user = $request->user();
        if (!$user->hasRole('admin')) { // Assume role is stored in User model
            abort(403);
        }
        return $next($request);
    }
    

Gotchas and Tips

Pitfalls

  1. Token Expiry Handling

    • Keycloak access tokens expire quickly (default: 5 minutes). Always store and refresh the refresh_token:
      $token = $provider->getAccessToken('refresh_token', [
          'refresh_token' => $storedRefreshToken
      ]);
      
    • Tip: Use Laravel’s auth:refresh event to silently refresh tokens in the background.
  2. Scopes and Permissions

    • Ensure your Keycloak client has the required scopes (openid, profile, email, etc.) configured in the Keycloak admin console.
    • Gotcha: Missing scopes will result in empty or partial user data.
  3. Realm vs. Domain Configuration

    • The domain in the config is optional but required if your Keycloak instance uses a custom domain (e.g., auth.example.com). Omit it if using a direct URL (e.g., https://keycloak.example.com/auth).
  4. CSRF and State Parameters

    • Always include a state parameter in the authorization URL to prevent CSRF attacks:
      $auth
      
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope