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

ksami/keycloak-security-bundle

Symfony bundle that integrates Keycloak with your application's security layer, enabling OpenID Connect/SSO authentication, role mapping, and access control. Simplifies configuring Keycloak as an identity provider for Symfony apps.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wires Laravel's auth layer to Keycloak so that an incoming bearer access token is validated and turned into an authenticated user, without you hand-rolling JWT parsing. Start here:

  1. Install and publish config
    composer require <vendor>/<package>   # must be >= v1.1.1 if your realm runs Keycloak 10+
    php artisan vendor:publish --tag=keycloak-config
    
  2. Read config/keycloak.php first. The only values you normally need are the realm base URL, realm name, client id, and client secret (only if you use the confidential/introspection path). Everything else is tuning.
  3. Register the guard in config/auth.php:
    'guards' => [
        'api' => ['driver' => 'keycloak', 'provider' => 'users'],
    ],
    
    Keep a users provider even if you never hit the database — the guard needs a provider to resolve or instantiate the user model.
  4. Protect routes with the standard auth:api middleware. Nothing Keycloak-specific is required:
    Route::middleware('auth:api')->get('/me', fn (Request $r) => $r->user());
    

First use case: an SPA or mobile app obtains a token from Keycloak, sends Authorization: Bearer <token>, and your Laravel API returns the mapped user plus their realm/client roles. Get that single /me endpoint working end-to-end before touching roles or user provisioning.

Version pinning is the first decision. v1.1.1 exists specifically to fix behaviour against Keycloak >= v10, where the token claim layout shifted. If your realm is Keycloak 10 or newer, pin >=1.1.1 in composer.json and verify with a decoded token before shipping; older versions will silently mis-read roles or user attributes.

Implementation Patterns

User resolution: stateless vs. local records. The guard can operate in two modes, and picking one up front avoids churn later:

  • Stateless — build a lightweight user object from token claims (sub, preferred_username, email) on every request. Fast, no writes, but no relationships, no id foreign keys, and no can() against persisted models.
  • JIT provisioning — map sub to a keycloak_id column and firstOrCreate() on login. Do this in a small listener or a custom user provider so the guard stays untouched:
    // AppServiceProvider or a dedicated resolver
    $user = User::firstOrCreate(
        ['keycloak_id' => $token->sub],
        ['name' => $token->name, 'email' => $token->email]
    );
    
    Make keycloak_id unique and indexed, and never key off email — emails change, sub does not.

Role and permission mapping. Realm roles and client roles live in different claim branches, so normalise them once into a Gate rather than scattering in_array() checks:

Gate::define('admin', fn ($user) => in_array('admin', $user->roles ?? []));

Then use $this->authorize('admin') or @can('admin') as normal. If your package exposes a hasRole()/hasAnyRole() helper on the user model, prefer it over raw claim access — it is the part most likely to be patched for Keycloak version differences.

Coarse gate + fine policy. A common split: middleware enforces "is authenticated and has the api-user client role" at the route-group level, while Policies handle per-resource ownership. This keeps token validation concerns out of business logic and makes the app testable without Keycloak in the loop.

Service-to-service tokens. For machine clients using client-credentials grants, there is usually no realm_access block on the token. Guard against this by defaulting role lookups to an empty array rather than assuming the key exists — a null here throws inside middleware and produces a confusing 500 instead of a clean 403.

Multi-guard apps. If you also have session-based web auth, register the Keycloak guard under a distinct name and never let web and api share a provider that assumes a password column. Mixing them is the most frequent source of "user is null but the token is valid" reports.

Testing. Do not boot Keycloak in CI. Either:

  • Http::fake() the JWKS/public-key or introspection endpoint with a fixture response, then sign a token with a test keypair; or
  • bypass the guard entirely in feature tests with $this->actingAs($user, 'api') and cover token validation separately in a handful of focused unit tests (valid, expired, wrong audience, wrong signature).

Keep a small fixture set of real tokens from each Keycloak version you support — this is what catches regressions like the v10 claim change before production does.

Gotchas and Tips

  • Keycloak version drift is the #1 failure mode. The v1.1.1 fix for Keycloak >= v10 is exactly this class of bug. Before upgrading Keycloak, decode a fresh token at jwt.io or via base64 -d and diff the claim names against what your pin of this package expects. Claim locations, not just values, have moved between versions.
  • Pin both sides. Record the supported Keycloak range in your README next to the package constraint. An unpinned package plus an upgraded IdP is a silent-auth-failure generator.
  • Signature key rotation. If the package caches the realm public key, a Keycloak key rotation invalidates the cache and every request 401s until TTL expiry. Know the cache key and TTL, and have a documented flush (php artisan cache:clear or a narrower tag flush) for incidents.
  • Clock skew. Container clocks drift. A token that is valid at the IdP can be "not yet valid" in your app. Allow a small leeway in config if exposed, and check exp/nbf claims when debugging intermittent 401s on otherwise-fine tokens.
  • Audience and issuer validation. Many misconfigurations are really "the token is for the wrong client." Confirm iss matches the realm URL exactly (trailing slash and http vs https both bite) and that the audience check targets your client id, not account.
  • Opaque tokens vs JWT. If your client is configured for bearer-only/introspection, each request performs a round trip to Keycloak. That is fine but it makes Keycloak a hard runtime dependency — a down IdP becomes a total API outage. Consider caching introspection results for short windows if the package supports it, and set aggressive timeouts.
  • Large tokens and header limits. Group-heavy realms can produce multi-KB tokens; behind nginx or a load balancer you may hit header-size limits and see 400s before Laravel ever runs. Increase buffer sizes or request fewer claims via scopes.
  • email and name are not guaranteed. They appear only if the corresponding scopes are requested and the user has them set. Always null-coalesce when provisioning users, and don't make them NOT NULL without a fallback.
  • Local logout does not log out Keycloak. Auth::logout() only drops the guard state; the token remains valid until expiry. Real logout needs the IdP's end-session endpoint plus client-side token discard.
  • Debugging 401s: log the decoded claim set (never the raw token) behind a temporary debug flag, and check the guard's exception type — expired, malformed, signature, and audience failures should be distinguishable in logs or you will be guessing.
  • Extension points worth knowing early: the user provider/resolver hook, the role-claim path config, and the HTTP client used for JWKS/introspection. Those three cover nearly every customisation request; if a fix requires editing package source, open an upstream PR instead — this package has already shown willingness to patch for IdP version changes, and a fork will cost you on the next release.
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