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 Laravel Package

socialiteproviders/keycloak

Keycloak OAuth2 provider for Laravel Socialite. Adds a Keycloak driver so your app can authenticate users through a self-hosted or cloud Keycloak realm, handling the OAuth redirect, callback, and user retrieval with Socialite's familiar API.

Deep Wiki
Context7

Getting Started

  1. Install the split package (pull in Socialite and the manager if you don't already have them):

    composer require socialiteproviders/keycloak
    

    You will also need laravel/socialite and socialiteproviders/manager — the split only ships the Keycloak provider class.

  2. Register the provider listener. On Laravel ≤10, add to app/Providers/EventServiceProvider.php:

    protected $listen = [
        \SocialiteProviders\Manager\SocialiteWasCalled::class => [
            \SocialiteProviders\Keycloak\KeycloakExtendSocialite::class.'@handle',
        ],
    ];
    

    On Laravel 11+ (no default EventServiceProvider), do it in AppServiceProvider::boot():

    use Illuminate\Support\Facades\Event;
    use SocialiteProviders\Manager\SocialiteWasCalled;
    use SocialiteProviders\Keycloak\KeycloakExtendSocialite;
    
    Event::listen(SocialiteWasCalled::class, [KeycloakExtendSocialite::class, 'handle']);
    
  3. Add credentials to config/services.php (env-backed is easiest):

    'keycloak' => [
        'client_id'     => env('KEYCLOAK_CLIENT_ID'),
        'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
        'redirect'      => env('KEYCLOAK_REDIRECT_URI'),
        'base_url'      => env('KEYCLOAK_BASE_URL'),   // e.g. https://sso.example.com
        'realms'        => env('KEYCLOAK_REALM'),      // e.g. my-realm
    ],
    

    Double-check the exact key names against the README in SocialiteProviders/Providers — older versions of the split used a slightly different shape.

  4. Minimal controller:

    Route::get('/auth/redirect', fn () => Socialite::driver('keycloak')->redirect());
    Route::get('/auth/callback', function () {
        $kcUser = Socialite::driver('keycloak')->user();
        // $kcUser->getId()        -> "sub" (stable, use this as your key)
        // $kcUser->getNickname()  -> "preferred_username"
        // $kcUser->getEmail(), getName(), getAvatar()
        // $kcUser->getRaw()       -> full userinfo payload
    });
    
  5. Where to look first. This repo is a read-only subtree split of SocialiteProviders/Providers. The real README, issues, and PR history live in the monorepo — read there before opening anything here. The provider class itself (Provider.php) is short; skim it to confirm which config keys and endpoints are actually used by your version.

  6. First use case: the "log in with Keycloak" button. Configure a confidential client in your Keycloak realm, set its Valid Redirect URIs to exactly your callback URL, and you should be able to complete a login in under ten minutes.

Implementation Patterns

Identify users by sub, not email. $user->getId() maps to the OIDC sub claim, which is stable across username/email changes. Store it as a unique column (keycloak_id or similar) and reconcile on it. Email changes happen in real deployments and will otherwise orphan accounts.

Map claims to your user model explicitly. A common pattern:

$kc = Socialite::driver('keycloak')->user();

$user = User::updateOrCreate(
    ['keycloak_id' => $kc->getId()],
    [
        'name'  => $kc->getName() ?? $kc->getNickname(),
        'email' => $kc->getEmail(),
    ]
);

Auth::login($user);

Stateless for API / SPA backends. If the frontend is a SPA or mobile app, call ->stateless() so Socialite doesn't try to use a session state:

return Socialite::driver('keycloak')->stateless()->redirect();
// and on callback:
$user = Socialite::driver('keycloak')->stateless()->user();

This is common when Keycloak is the OIDC authority for an SPA but you still want a Laravel session on callback.

Pull roles/groups out of the raw payload, or decode the access token. $user->getRaw() gives you the userinfo response. Keycloak roles (realm_access.roles, resource_access.<client>.roles, groups) frequently live in the access token / ID token rather than userinfo. Two practical options:

  • Request the openid scope and decode the ID token ($user->accessTokenResponseBody['id_token']) to read claims.
  • Or call Keycloak's token introspection / admin API separately, using the access token from $user->accessTokenResponseBody['access_token'].

Roles are not modelled by the provider — plan to do this mapping yourself.

Logout is not handled by the provider. Keycloak uses the end-session endpoint, so log the user out of Laravel then redirect:

Auth::logout();
$url = config('services.keycloak.base_url')
    .'/realms/'.config('services.keycloak.realms')
    .'/protocol/openid-connect/logout?redirect_uri='.urlencode(route('home'));
return redirect()->away($url);

Multi-tenant / multi-realm. The provider reads config at build time, so for dynamic realms use Socialite::buildProvider(Provider::class, $config) with a per-tenant array, or register additional driver names via Socialite::extend.

Downstream API calls. Grab the access token from $user->accessTokenResponseBody['access_token'] (or $user->token) and attach it as Bearer when calling services behind Keycloak. Remember Keycloak access tokens are short-lived (default 5 minutes) — plan token refresh yourself if you need long-lived access; the provider does not refresh for you.

Testing. Don't hit a real Keycloak in tests. Mock the driver:

Socialite::shouldReceive('driver->user')->andReturn($fakeUser);

Or better, put the callback logic behind a small service and test the service with a Laravel\Socialite\Two\User stub.

Gotchas and Tips

  • Subtree split, not the source of truth. This repo has no issues/PRs workflow of its own. File bugs and send patches to SocialiteProviders/Providers. If you need a fix, fork the monorepo, not this repo.
  • base_url and the /auth prefix. Keycloak 17+ dropped the /auth path segment from the base URL; older Quarkus builds kept it. If you get 404s from /realms/.../protocol/openid-connect/auth, your base_url almost certainly has an extra or missing /auth. Test the endpoint by hand with curl first.
  • "Driver [keycloak] is not supported." Almost always a missing event listener. On Laravel 11 the default EventServiceProvider no longer exists, so the classic snippet silently does nothing — register in AppServiceProvider::boot() instead.
  • Redirect URI must match byte-for-byte. Keycloak's "Valid Redirect URIs" is exact match (with limited wildcard support). Trailing slashes, http vs https, and www vs bare host all break the flow with a Keycloak-side 400 that gives you almost no info. Log the URL being requested and compare it to what's in Keycloak.
  • openid scope is mandatory for Keycloak to return an ID token / proper OIDC flow. Default scope in the provider includes it, but if you override scope in config/services.php and drop openid, you'll get weird failures downstream.
  • Package last released April 2023. It is stable but not actively updated. Before adopting on Laravel 11/12, verify compatibility with your installed laravel/socialite version; if composer require complains about constraints, you may need --with-all-dependencies or a temporary fork. The provider itself is tiny — patching a version constraint locally is trivial.
  • Config cache bite. config/services.php is cached; after adding or editing the keycloak block run php artisan config:clear (or config:cache in prod) or you'll get mysterious "missing config" errors.
  • Userinfo ≠ ID token. The provider's user() typically calls the userinfo endpoint. Claims present in the ID token (roles, groups, custom attributes) may not be present in getRaw(). Don't assume everything your Keycloak mapper configured will show up there — inspect the raw response before building logic on a claim.
  • SSL verification. If your Keycloak uses a self-signed cert in dev, Socialite's Guzzle client will reject it. Either trust the cert on the dev box, or pass a custom client: Socialite::driver('keycloak')->setHttpClient(new \GuzzleHttp\Client(['verify' => false])) — dev only, never production.
  • Client type matters. Confidential clients need the client_secret; public clients won't use it and may require PKCE (enable-pkce in Keycloak). Socialite's flow is authorization-code based, not PKCE by default, so use a confidential client for server-side Laravel apps.
  • Debugging tips. Enable Keycloak "Events" logging on the client/realm to see why a redirect was rejected. On the Laravel side, dd($user->getRaw()) and the full accessTokenResponseBody is the fastest way to understand what your realm actually returns.
  • Extension point. If you need extra scopes, custom endpoints, or PKCE, subclass \SocialiteProviders\Keycloak\Provider and register it as the 'keycloak' driver via Socialite::extend('keycloak', ...). That's cleaner than patching the vendored split.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource