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

Symfony Oidc Bundle Laravel Package

drenso/symfony-oidc-bundle

Symfony OIDC bundle adds OpenID Connect authentication to Symfony applications using the new authentication manager. Supports SURFconext/OpenConext, Keycloak, Auth0, Microsoft Entra ID and others. Requires Symfony 5.4+ and PHP 8.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and scaffold. composer require drenso/symfony-oidc-bundle. With Symfony Flex you get config/packages/drenso_oidc.yaml and matching env vars (OIDC_WELL_KNOWN_URL, OIDC_CLIENT_ID, OIDC_CLIENT_SECRET) appended to .env. Without Flex, create the config file manually — that is the whole install surface.

Where to look first, in order:

  1. config/packages/drenso_oidc.yaml — confirm the default client block resolves from env.
  2. Your App\Entity\User / user provider — this is where most of the real work lives.
  3. config/packages/security.yaml — the oidc: firewall listener.
  4. docs/ms-entra-id.md and docs/adfs.md — only if you are on those IdPs; the defaults will not work for Entra ID.

First use case — wire the three moving parts.

Client config (already generated by Flex):

drenso_oidc:
    clients:
        default:
            well_known_url: '%env(OIDC_WELL_KNOWN_URL)%'
            client_id: '%env(OIDC_CLIENT_ID)%'
            client_secret: '%env(OIDC_CLIENT_SECRET)%'

User provider must implement OidcUserProviderInterface:

public function ensureUserExists(string $userIdentifier, OidcUserData $userData, OidcTokens $tokens): void
{
    // bootstrap account on first login; throw if provisioning is disallowed
}

public function loadOidcUser(string $userIdentifier): UserInterface
{
    return $this->loadUserByIdentifier($userIdentifier);
}

Firewall + login route:

security:
    firewalls:
        main:
            pattern: ^/
            oidc: ~
#[Route('/login_oidc', name: 'login_oidc')]
#[IsGranted('PUBLIC_ACCESS')]
public function login(OidcClientInterface $oidcClient): RedirectResponse
{
    return $oidcClient->generateAuthorizationRedirect();
}

The authenticator only accepts the callback on check_path (default /login_check), which must match the redirect URI registered at the IdP. Set both and you have a working login on day one.

Implementation Patterns

Multi-tenant / multi-IdP via named clients. Any extra client under clients: is autowirable by name: OidcClientInterface $linkOidcClient. Anything that does not match a configured name silently resolves to the default client, so keep client names distinct from any future class/service names and verify with debug:container when autowiring surprises you. Pair this with a per-firewall client: value to build separate login entry points (staff vs. partner IdP) in one application.

Route the login entry through a controller, not the firewall. generateAuthorizationRedirect() accepts prompt, scopes and arbitrary query params — this is the extension point for prompt=login, acr_values, login_hint, or locale negotiation. Do the IdP-specific choice there rather than scattering it into security config.

Choose your identity source deliberately. Three switches interact:

  • user_identifier_property (default sub, supports Symfony PropertyAccessor paths, object-based access)
  • user_identifier_from_idtoken (default false — identifier comes from the userinfo endpoint)
  • enable_retrieve_user_info / user_info_from_idtoken

For id_token-only flows (saves an HTTP round-trip per login, common with Entra ID), set enable_retrieve_user_info: false and user_identifier_from_idtoken: true. For richer profile data, keep the default. For IdPs with flaky userinfo endpoints, user_info_from_idtoken: true lets you keep the endpoint enabled but read claims from the token.

Separate loadOidcUser from loadUserByIdentifier. The dedicated method exists so you can distinguish OIDC-originated logins from form logins (different password/credential handling, different failure messages, different audit logs) inside one provider. Forwarding is fine only if OIDC identifiers are globally unique in your table.

Remember me. Add _remember_me=1 to the URL that hits your login controller, set enable_remember_me: true on the listener, and ensure Symfony's remember_me authenticator is configured. Override the parameter name per client with remember_me_parameter.

Logout. Set enable_end_session_listener: true plus logout: true on the firewall, and leave use_logout_target_path: true so the IdP bounces the user back. For per-client logout endpoints, the client's end_session_endpoint must appear in the well-known document.

Caching. Enable symfony/cache in the app and the bundle caches both well-known and JWKS for well_known_cache_time / jwks_cache_time (default 3600s). This is the single cheapest latency win on this integration — without it, the discovery and key fetches land on every login. Use a shared cache adapter in multi-node deployments.

PKCE. code_challenge_method: 'S256' where the IdP supports it; plain only for legacy providers. Treat this as default-on for public/SPA-adjacent deployments.

Gotchas and Tips

Version floor is hard. v2+ requires Symfony 5.4 (new authenticator manager) and PHP 8. On Symfony <6, enable_authenticator_manager: true is still needed or the oidc: listener is ignored entirely. If you are on 5.3/PHP 7.4, stay on the v1.x branch and its separate docs.

The check_path / redirect URI mismatch is the classic failure. If the IdP redirect URI and check_path disagree, the authenticator never accepts the callback and you get a redirect loop rather than an error. Check redirect_route in the client config against check_path in the firewall — both default to /login_check, so changing one without the other breaks silently.

Config is not per-client for the whole matrix. Authentication behavior (user_identifier_property, enable_remember_me, success/failure handlers) lives on the firewall listener, while token/discovery settings live on the client. With multiple clients behind one firewall you get one identity configuration; split firewalls if you need different identifier properties.

ensureUserExists is the security boundary. It runs during authentication and a throw there means hard auth failure — do not let a downstream exception (duplicate email, race on concurrent first login) surface as a generic 500. Wrap provisioning in a transaction and handle the uniqueness race explicitly.

Disabling userinfo has a hard precondition. Setting enable_retrieve_user_info: false without user_identifier_from_idtoken: true is an invalid combination; the bundle has no identifier source. Validate this in your test suite, not production.

IdP quirks are documented, not defaulted. Entra ID will not work out of the box (docs/ms-entra-id.md explains the required overrides). On-prem ADFS v1 needs allow_discovery_access_token_issuer: true to accept the non-standard access_token_issuer discovery field. Both are opt-in flags — expect the first Entra ID attempt to fail until you follow the doc.

Logout is disabled by default for a good reason. SSO logout only terminates the IdP session for the current app; the user may remain signed in elsewhere. Enabling enable_end_session_listener gives a false sense of security unless your app's own session is also destroyed. Document the gap rather than pretending it closes.

Debug sequence. bin/console debug:config drenso_oidc (resolved client values), debug:firewall main (listener registered?), then check the redirect URI echoed in the authorization URL. Most failures are resolved-value failures (env var not interpolated, trailing slash, http/https mismatch) rather than code issues.

Extension points worth knowing. well_known_parser accepts a service ID for a custom parser — the right hook when an IdP publishes a malformed or non-standard discovery document. custom_client_headers gets you through proxies and IdPs requiring extra headers. token_leeway_seconds (default 300) absorbs clock skew in token validation; raise it only when you can measure skew, never as a blind fix for nbf/iat errors.

Support posture. 99 stars, zero dependent packages, and the repository is a mirror of an internal GitLab instance — the maintainers accept issues/PRs, but do not expect a broad third-party ecosystem or quick community answers. Budget for reading the bundle source (OidcClient, OidcAuthenticator, OidcTokens) when you hit an edge case; it is a thin OO wrapper over jumbojett/OpenID-Connect-PHP, so behavior usually traces directly there. Apache-2.0 licensing is unproblematic for commercial use.

Testing. The authorization redirect is cheap to unit test — inject the client, assert on the URL/state param. The full callback flow needs a stubbed IdP (a local symfony/http-client mock adapter against the well-known/JWKS endpoints) so you exercise signature validation rather than bypassing it.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator