hslavich/oneloginsaml-bundle
A Symfony bundle for adding SAML 2.0 single sign-on via the OneLogin PHP SAML toolkit. Configure your identity provider, generate service provider metadata, and handle SP-initiated or IdP-initiated login and logout with attribute and assertion support.
Framework reality check first. hslavich/oneloginsaml-bundle is a Symfony bundle, not a Laravel package. It extends Symfony\Component\HttpKernel\Bundle\Bundle, registers services through the Symfony DI container, and hooks into Symfony's security firewall via a custom firewall factory. There is no Laravel service provider, no auto-discovery, and no config publishing. In a Laravel app this package is effectively inert — you would be installing Symfony components for nothing. If your day job is Laravel-only, the honest advice is: don't install this; use the underlying onelogin/php-saml library directly (covered under Gotchas). The rest of this assessment assumes a Symfony context, since that is the only place the bundle does anything.
Minimal path to first login:
composer require hslavich/oneloginsaml-bundleconfig/bundles.php: OneLoginSamlBundle\OneLoginSamlBundle::class => ['all' => true]. Without this, nothing boots.php bin/console config:dump-reference onelogin_saml — this is the first place to look. It reflects the exact keys your installed version supports (they differ between 1.x and 2.x, and between snake_case and camelCase).config/packages/onelogin_saml.yaml with idp, sp, and security blocks:onelogin_saml:
idp:
entityId: 'https://idp.example.com/metadata'
singleSignOnService:
url: 'https://idp.example.com/sso'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
singleLogoutService:
url: 'https://idp.example.com/slo'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
x509cert: '%env(SAML_IDP_CERT)%'
sp:
entityId: 'https://app.example.com/saml/metadata'
assertionConsumerService:
url: 'https://app.example.com/saml/acs'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
singleLogoutService:
url: 'https://app.example.com/saml/slo'
binding: 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
privateKey: '%env(SAML_SP_KEY)%'
x509cert: '%env(SAML_SP_CERT)%'
security:
wantAssertionsSigned: true
authnRequestsSigned: true
username_attribute: 'email'
openssl req -x509 -newkey rsa:2048 -nodes -days 3652 ...) and give the IdP admin your SP metadata plus ACS URL.SamlUserInterface on your User entity (getUsername(), setSamlAttributes(array $attributes)), and SamlUserFactoryInterface::createUser(string $username, array $attributes): SamlUserInterface as a service.security:
providers:
saml_provider:
id: onelogin_saml.user_provider
firewalls:
main:
saml:
provider: saml_provider
user_factory: App\Security\SamlUserFactory
The first use case to get working is SP-initiated login: user hits your /saml/login action → bundle's OneLogin\Saml2\Auth service builds an AuthnRequest → IdP redirects back with a POST to /saml/acs → the bundle parses and validates the response, calls your factory with the NameID/username and the raw attribute array → you end up with a Symfony token. That loop is 90% of the integration.
createUser() is where you do find-or-create by NameID/email against your user table (JIT provisioning), and where you persist SAML attributes onto the user. Keep the factory small and pure — it's the easiest part of the system to unit test with fixture attribute arrays.security.yaml. Derive getRoles() from IdP group attributes inside the factory so IdP-side changes take effect on next login. If you need near-immediate revocation, cap session lifetime or re-read roles in refreshUser()./saml/login, /saml/acs, /saml/slo, /saml/metadata). Inject the OneLogin\Saml2\Auth service (id onelogin_saml.auth — confirm with bin/console debug:container | grep -i onelogin) and call login(), processResponse(), processSLO(), logout(). The ACS must be POST-only.OneLogin\Saml2\Settings from the same config array, then getSPMetadata() + validateMetadata() and return XML. Cache it; it only changes on cert rotation.urn:oid:... attribute names through controllers. Parse the raw array into a small DTO in the factory, store the raw array too for debugging and audit.InResponseTo to verify. Rely on assertion signature validation and configure a default ACS/consumer URL at the IdP./logout alone leaves the IdP session alive. Wire processSLO() both ways and use a LogoutEvent listener for app-specific session cleanup.onelogin/php-saml and construct Auth from dynamically built Settings — at that point the bundle is just getting in your way.config/packages/dev/onelogin_saml.yaml pointing at a test IdP.composer require onelogin/php-saml, build the settings array in config/services.php, wrap Auth::login() / Auth::processResponse() in a controller, and store the NameID in session. This bundle cannot be adapted without effectively embedding a Symfony kernel.composer why-not hslavich/oneloginsaml-bundle against your Symfony and PHP versions before committing — Symfony 7 / PHP 8.3 are likely outside the constraint set. Budget for a fork or a pin.onelogin/php-saml (now community-maintained under the SAML-Toolkits org). Security fixes land in the library, not the bundle; audit the library version directly.|), keep PEM headers consistent, and remember the IdP cert is the signing cert, not its TLS cert. Your SP private key must match your SP public cert — mismatch produces confusing signature failures.http vs https, www, and port numbers are the #1 cause of "Invalid response".Destination/ACS from the request. Behind a load balancer, set baseurl or Symfony trusted_proxies + x-forwarded-*, or you'll get destination/signature mismatches that look like IdP problems.wantAssertionsSigned, wantMessagesSigned, authnRequestsSigned, logoutRequestSigned. Most IdPs sign only the assertion; turning on wantMessagesSigned without the IdP signing the response breaks login. Change one flag at a time.getLastErrorReason().$auth->getLastErrorReason(), $auth->getLastErrorException(), $auth->getErrors() in the ACS controller before rethrowing. bin/console debug:config onelogin_saml shows resolved config (secrets masked); debug:container shows real service IDs, which differ by version.InResponseTo validation is session-based. Don't put these endpoints on a stateless firewall, and clear cookies between manual test runs.OneLogin\Saml2\Auth, and the bundle's security events. If none fit (dynamic IdPs, per-tenant certs), take the library directly — the bundle's value is firewall glue, not protocol handling.How can I help you explore Laravel packages today?