bloomatwork/oneloginsaml-bundle
A Symfony bundle that integrates the OneLogin SAML PHP toolkit, adding SAML 2.0 single sign-on authentication to Symfony applications. It helps configure service provider settings, handle SAML requests and responses, and authenticate users via an identity provider.
First, the framework reality check. bloomatwork/oneloginsaml-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/onelogin_saml.php. You cannot composer require it into a Laravel app and expect it to boot — its public surface is a Bundle subclass, a DependencyInjection extension, and a SecurityBundle firewall factory. So "getting started" for a Laravel developer means deciding what you actually adopt:
onelogin/php-saml, which is framework-agnostic and perfectly usable in Laravel. Run composer require onelogin/php-saml and wire it yourself — this is the path that matches the bundle's own architecture without dragging in Symfony.DependencyInjection/Configuration.php (the exact settings tree — SP entity ID, ACS URL, IdP certs, baseurl), Security/Authentication/Provider/OneloginSamlProvider (how the SAML response becomes a token/user), and the shipped controller for the ACS and metadata endpoints. That's essentially the spec for what you'll reimplement as a Laravel controller + middleware.Bundle class. Treat this repo as a snapshot of an approach, not a maintained dependency. Verify the onelogin/php-saml version referenced in its composer.json and cross-check that version's CVE history before committing.Minimal first use case (SP-initiated login in Laravel):
OneLogin_Saml2_Auth with an SP + IdP settings array (entity ID, ACS URL, IdP SSO URL, X.509 signing cert). Keep this array in config/saml.php and env vars — not hardcoded.GET /saml/login → $auth->login() (redirect to IdP), and POST /saml/acs → $auth->processResponse() then Auth::login($user).GET /saml/metadata returning $auth->getSettings()->getSPMetadata() so your IdP admin can register you.GET /saml/logout → $auth->logout() if you need SLO.Once that round-trip works — protected route → IdP redirect → ACS POST → authenticated session — everything else (IdP-initiated, logout, attribute mapping, multi-tenant) is incremental.
Where to look first: the bundle's Configuration.php for the settings keys, the README for the YAML example (translate it 1:1 into a PHP array), and onelogin/php-saml's own settings_example.php in its repo — that's the authoritative source for what the settings array must contain.
The user-mapping seam is yours to own. The Symfony bundle ships a SamlUserFactory interface precisely because the SAML response → application User conversion is app-specific. In Laravel the equivalent is a dedicated service, e.g. App\Services\Saml\UserProvisioner, invoked from the ACS controller:
public function provision(array $attributes, string $nameId): User
{
$email = $attributes['mail'][0]
?? $attributes['emailaddress'][0]
?? $attributes['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'][0]
?? throw new SamlAttributeException('No email attribute in assertion');
$user = User::firstOrNew(['email' => $email]);
$user->name ??= $attributes['displayName'][0] ?? $email;
$user->saml_name_id = $nameId;
$user->save();
return $user;
}
Keep the attribute-name map in config (config/saml.php → attribute_map), not in code. IdPs disagree wildly on mail vs emailaddress vs a full SOAP namespace URI — this is the single biggest source of "works with Okta, breaks with Entra" bugs.
JIT provisioning vs. lookup-only. For internal apps, firstOrNew above (just-in-time provisioning) is the pragmatic default and the bundle's provider pattern supports it. For anything sensitive, do lookup-only and fail closed — an unknown IdP user must not self-register into your app just because the IdP authenticated them. Make this a config flag, not a code fork.
Both flow directions. SP-initiated is the normal redirect dance. IdP-initiated (user clicks the app tile in Okta) POSTs straight to your ACS with no RelayState / in-flight AuthnRequest. Test both explicitly — teams routinely ship SP-initiated and discover IdP-initiated is broken in production. For IdP-initiated, set an explicit default landing route rather than depending on RelayState being present.
Logout: local-first. SAML SLO is brittle and depends on the IdP's SLO endpoint behaving. Many teams do local logout + redirect to the IdP's logout URL and treat federated SLO as best-effort. In the Symfony bundle this maps to logout.success_handler; in Laravel it's your LogoutController deciding between Auth::logout() + redirect vs. $auth->logout().
Testing: never hit a real IdP in CI. Capture one real signed SAML response as a fixture, then inject it into processResponse() (or POST it to the ACS route) in feature tests. Wrap the toolkit behind a small SamlAuthenticatorInterface of your own so you can swap to a maintained Laravel SAML package later without touching controllers — this is exactly the decoupling the Symfony bundle almost provides and you should provide deliberately.
Metadata as a config artifact. Serve /saml/metadata for initial IdP registration, but store the IdP's metadata XML in your repo or config store so certificate rotation is a config change and a deploy, not a code change.
php artisan vendor:publish, no auto-registered routes, no container bindings. Any tutorial you find for this bundle will be Symfony-shaped (AppKernel, security.yaml, onelogin_saml:). Translate the concepts, not the configuration.onelogin/php-saml (actively maintained) and, if you ever move to Symfony, hslavich/OneloginSamlBundle or nbgrp/onelogin-saml-bundle. Pin the toolkit version and watch its CVE feed.-----BEGIN CERTIFICATE----- headers, no newlines) for most config keys — but some keys expect full PEM. Getting this wrong produces "invalid signature" errors, not "bad certificate", which sends you debugging the wrong layer. Copy the format from the toolkit's settings_example.php verbatim.sp.entityId and sp.assertionConsumerService.url must match exactly what's registered at the IdP — trailing slash, http vs https, port. This is the most common "works on my machine" failure. Compare the assertion's <Audience> and <Recipient> against your config character by character.NotBefore / NotOnOrAfter. A few seconds of drift between IdP and SP throws OneLogin_Saml2_ValidationError. Ensure NTP is synced on both boxes and consider a small skew allowance in toolkit settings.mail) or OID (urn:oid:0.9.2342...) or full URI. Always isset($attributes[$key][0]), never assume a scalar. Log the full $attributes dump once during setup (with debug: true in toolkit settings) to see what your IdP actually sends, then turn debug off.baseurl behind a proxy / subpath. The toolkit builds ACS and SLO URLs from config. Behind a load balancer, reverse proxy, or subdirectory mount, set the base URL explicitly or the ACS URL in the assertion won't match. In Laravel, also ensure TrustProxies is configured — otherwise url() helpers generate the wrong scheme/host in redirects.SameSite=Strict on the session cookie drops it, and you land on the ACS unauthenticated. Use Lax (or None + Secure) for the session cookie and confirm RelayState survives the round-trip.app/Http/Middleware/VerifyCsrfToken::$except). Also exempt it from any CSP that blocks form posts, and keep WAFs from body-inspecting and re-encoding the assertion (whitespace changes break canonicalization and thus signature validation).debug: true, then read getLastErrorReason() and getLastErrorException() — that pair is usually enough to distinguish signature vs. audience vs. recipient failures. Never leave debug on in production: it writes full assertions (containing PII and session identifiers) to logs.ReplayCache / a cache store if your php-saml version supports it. Replay protection is genuinely worth implementing for anything customer-facing.How can I help you explore Laravel packages today?