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

nti/keycloak-security-bundle

nti/keycloak-security-bundle integrates Keycloak authentication with Symfony applications, helping configure login, token handling, and access control for services protected by Keycloak.

View on GitHub
Deep Wiki
Context7

Getting Started

Stack check before anything else. nti/keycloak-security-bundle is a Symfony bundle (IDCI\Bundle\KeycloakSecurityBundle), not a Laravel package. There is no service provider, no facade, no config/keycloak.php; it wires itself into Symfony's DI container, routing, and the Security component (authenticator + user provider). You cannot composer require it into Laravel and get anything working — you'll just pull in symfony/security-*, symfony/http-kernel, and symfony/framework-bundle and fight illuminate/* version conflicts. If you're actually on Laravel, treat this bundle as a reference implementation of the authorization-code flow and use stevenmaguire/oauth2-keycloak (which this bundle wraps) or robsontenorio/laravel-keycloak-guard instead. The steps below assume Symfony.

What v2.0.0 changed — read this before upgrading. The release notes are one line: "Adapting to version 24 of keycloak and some minor corrections." A major bump with no changelog means you must treat it as breaking until proven otherwise. Concretely, expect (and verify against the tag):

  1. Security layer moved off Guard. Symfony 6.x/7 removed GuardAuthenticator, so a "Keycloak 24 + Symfony" refresh almost certainly means the authenticator now implements AuthenticatorInterface. If so, the firewall key changes from guard: { authenticators: [...] } to custom_authenticators: [...].
  2. Minimum PHP/Symfony bumped. Check composer.json in the v2 tag — you likely need PHP 8.1+ and Symfony 6.4/7. If you're stuck on Symfony 5.4, you stay on v1 and accept that Keycloak 24 may still break it server-side.
  3. Service IDs and config keys may have been renamed. Diff DependencyInjection/Configuration.php and Resources/config/services.yaml between your v1 tag and v2 before you touch YAML.

Minimal path to first login:

  1. Read composer.json in the tag you're installing — confirm symfony/* and php constraints. Pick v2 for Symfony 6.4/7, v1 for 5.4.
  2. composer require nti/keycloak-security-bundle:^2.0 (pin the exact tag if you're risk-averse).
  3. Enable it in config/bundles.php:
    IDCI\Bundle\KeycloakSecurityBundle\IDCIKeycloakSecurityBundle::class => ['all' => true],
    
  4. In Keycloak 24, create a confidential client with Standard Flow enabled. Register the exact callback URL (e.g. https://app.test/keycloak/check) under Valid Redirect URIs, and set Web Origins.
  5. Configure the bundle (confirm key names against DependencyInjection/Configuration.php — the README drifts between versions, and v2 is a rename opportunity):
    # config/packages/idci_keycloak_security.yaml
    idci_keycloak_security:
        server_url: '%env(KEYCLOAK_SERVER_URL)%'          # back-channel, e.g. http://keycloak:8080
        server_public_url: '%env(KEYCLOAK_PUBLIC_URL)%'   # browser-facing, e.g. https://auth.example.com
        realm: '%env(KEYCLOAK_REALM)%'
        client_id: '%env(KEYCLOAK_CLIENT_ID)%'
        client_secret: '%env(KEYCLOAK_CLIENT_SECRET)%'
        redirect_route: 'idci_keycloak_security_check'
    
  6. Import the bundle's routes (config/routes/idci_keycloak.yaml) so the login/check endpoints exist.
  7. Point your firewall at the bundle's provider and authenticator — check the installed version first:
    # config/packages/security.yaml — v2 / Symfony 6.4+
    security:
        providers:
            keycloak:
                id: idci_keycloak_security.provider.keycloak_user
        firewalls:
            main:
                provider: keycloak
                custom_authenticators:
                    - idci_keycloak_security.authenticator.keycloak
    
    If debug:container shows the authenticator extending AbstractGuardAuthenticator, swap custom_authenticators for the guard block from your v1 config.

First use case: replace form_login with "Log in with Keycloak", have the bundle build a Symfony token from the Keycloak ID token, and map realm/client roles into Symfony roles so isGranted('ROLE_ADMIN') works unchanged.

Where to look first (in this order):

  • composer.json at the tag — version ceilings, and whether 2.0.0 dropped any symfony/* packages you depend on.
  • DependencyInjection/Configuration.php — the authoritative config schema; ignore stale README examples.
  • Resources/config/services.yaml — exact service IDs. Confirm with bin/console debug:container idci_keycloak_security.
  • Security/ — the authenticator and user provider. Guard vs AuthenticatorInterface determines your Symfony version ceiling; in v2 this is also where the Keycloak-24 handling (realm URLs, token claims) lives.
  • Client/ — the admin API wrapper (user CRUD, role assignment) if it's included in your version.

Implementation Patterns

Firewall + logout. Keep the bundle's authenticator as the only entry point on the firewall and wire logout so users leave cleanly (adjust guard: vs custom_authenticators: to what your installed version registers):

firewalls:
    main:
        provider: keycloak
        custom_authenticators: [idci_keycloak_security.authenticator.keycloak]
        entry_point: idci_keycloak_security.authenticator.keycloak
        logout:
            path: idci_keycloak_security_logout

Sync Keycloak identities to local users. The built-in provider returns a lightweight user object; most apps want a real User entity. Decorate or replace the provider (bind your class to the same service ID, or use decorates:), and upsert on first login keyed by sub (stable) or preferred_username (human-readable but renameable). Alternatively listen to SecurityEvents::INTERACTIVE_LOGIN / LoginSuccessEvent and upsert there — less invasive, but you lose control of provider lookups. The listener route survives the Guard→Authenticator migration better, so prefer it if you're upgrading a v1 integration to v2.

Role mapping. Keycloak puts roles in realm_access.roles and resource_access.{client}.roles. Decide once whether Symfony roles are derived at login time or read on every request from the token, and make Keycloak the single source of truth. Don't hand-maintain a parallel role table in your DB. Use Symfony's role_hierarchy to collapse Keycloak's many granular roles into a few app-level ones.

Admin API for provisioning. Inject the admin client and keep it out of request-hot paths:

use IDCI\Bundle\KeycloakSecurityBundle\Client\KeycloakClient;

final class KeycloakProvisioner
{
    public function __construct(private KeycloakClient $keycloak) {}

    public function invite(string $email): void
    {
        $this->keycloak->createUser([
            'username' => $email,
            'email' => $email,
            'enabled' => true,
            'requiredActions' => ['UPDATE_PASSWORD', 'VERIFY_EMAIL'],
        ]);
    }
}

Typical uses: invite flow, assign realm roles, disable on offboarding, search by email. Wrap calls in a small adapter so you can mock it in tests and swap it if the package goes stale.

Keycloak 24 provisioning caveat. KC 24 enables the declarative user profile by default. createUser payloads with attributes/fields outside the user-profile schema now fail with a 400 rather than being silently stored. Either add the attributes to the realm's user profile or set unmanagedAttributePolicy explicitly, and keep the payload to username/email/firstName/lastName unless you've configured more.

Local development with Docker. Run quay.io/keycloak/keycloak:24.0 start-dev, import a realm JSON into the repo, and set two URLs: server_public_url = http://localhost:8080 (browser redirects), server_url = http://keycloak:8080 (token exchange from the PHP container). This split is the single most common source of "connection refused" on callback. Keycloak 24's hostname v2 settings mean you should also pin KC_HOSTNAME (or KC_HOSTNAME_URL) consistently with server_public_url, otherwise issuer URLs in tokens won't match what the bundle expects.

Stateless APIs. This bundle is session/authorization-code oriented. For a SPA or mobile client hitting your API, don't try to bend the authenticator into a bearer-token validator — validate JWTs against Keycloak's JWKS endpoint with a cached key set and a kid-aware library. Keep the two flows separate.

Testing. In config/packages/test/security.yaml, swap in a fake provider/authenticator and use loginUser() where possible (easier on 6.4/7 than with Guard). Unit-test the admin client with a Guzzle MockHandler. Test the provider and client in isolation and keep the authenticator thin so there's little to cover.

Gotchas and Tips

  • GPL-3.0 is a real blocker, not a footnote. This is copyleft; linking it into a proprietary SaaS is a legal-review item. A private fork doesn't solve it. Re-verify the license on the 2.0.0 tag — license changes do happen around major releases, in both directions. If you can't ship GPL, reimplement the ~200 lines of OAuth glue yourself against stevenmaguire/oauth2-keycloak or league/oauth2-client.
  • Vendor risk just got louder, not quieter. A single one-line release after a long gap is a signal, not a comfort: there's no changelog, no migration guide, and a major version bump labelled "some minor corrections." Pin an exact tag or commit hash, fork it under your org, and diff the tag yourself before upgrading. Don't float on dev-main.
  • Guard removal. Symfony 6 removed GuardAuthenticator. bin/console debug:container idci_keycloak_security.authenticator.keycloak tells you immediately which interface the class implements in the version you installed — that decides guard: vs custom_authenticators: and your Symfony ceiling.
  • Don't upgrade the bundle and the Keycloak server in the same deploy. If 2.0.0 is your first Keycloak 24 target, change one variable at a time: server upgrade against v1 first (to see whether v1 still functions on 24), then bump the bundle. Otherwise every failure has two candidate causes.
  • Redirect URI must match byte-for-byte, including trailing slash and http vs https. Mismatches surface as a Keycloak error page, not a Symfony exception, so check the browser URL before digging into logs.
  • Missing role claims. If realm_access.roles is absent from the ID token, the realm-roles mapper isn't in the client scopes assigned to your client. Add/enable it and re-issue tokens — old tokens keep the old claims.
  • Proxy/HTTPS setups. Behind a load balancer, set framework.trusted_proxies and trusted_headers or you'll get secure-cookie and scheme mismatches that bounce users between login and callback with no error. Keycloak 24's stricter hostname/issuer validation makes a mismatched external URL fail harder than it used to.
  • Logout doesn't end the SSO session. Symfony's logout kills the local session; Keycloak's SSO cookie survives, so "log out → log in" silently re-authenticates. Redirect to /realms/{realm}/protocol/openid-connect/logout with id_token_hint and a registered post_logout_redirect_uri.
  • Admin API 403s. The service account needs realm-management roles (view-users, manage-users, view-realm) and "Service Accounts Enabled" on the client. A 401 usually means an expired cached admin token — wrap calls and refresh. A 400 on user creation on KC 24 is usually the declarative user profile, not permissions.
  • Token lifetime vs session lifetime. Keycloak's SSO Session Idle/Max can be shorter than the Symfony session, so admin calls made later in a session may lack a valid token. Don't assume the authenticator stores a refreshable token; if you persist access/refresh tokens in the session, encrypt them yourself and handle refresh explicitly.
  • Debug commands that save time: bin/console debug:config idci_keycloak_security (what config actually loaded), bin/console debug:router | grep -i keycloak, bin/console debug:container idci_keycloak_security, composer show nti/keycloak-security-bundle --all (see what you actually got), and decode the ID token (jwt.io or a local decode) to see exactly which claims the authenticator receives.
  • Extension points: decorate idci_keycloak_security.client.keycloak for retries/logging, implement your own UserProviderInterface for local user hydration, and subclass the authenticator only to customize the success handler (intended-URL redirect, audit logging) — the method name moved from onAuthenticationSuccess to onAuthenticationSuccess/AuthenticationSuccessEvent depending on the interface, so check the base class. Prefer decoration over editing vendored code.
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.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics