onelogin/php-saml
onelogin/php-saml adds SAML 2.0 SSO support to PHP applications, including SP- and IdP-initiated login, signed assertions, logout handling, strict mode, and configurable security checks. The 4.x branch supports PHP 7.3+ and PHP 8.x.
Architecture fit. onelogin/php-saml is a framework-agnostic SAML 2.0 SP toolkit, not a Laravel package. It provides the hard parts of the protocol — AuthnRequest construction, Response/Assertion signature and decryption validation, metadata generation and parsing, SLO message handling — while deliberately delegating session state and user persistence to the host app. That is the right seam for Laravel: we keep Eloquent users, guards, and policies, and use the library purely as a protocol engine behind our own controller and guard. The cost is that we own the integration layer: routing, CSRF exemptions, config assembly, replay cache, attribute mapping, and user provisioning are all ours to build. There is no Laravel service provider, facade, or config stub shipped upstream.
Integration feasibility. Technically straightforward. Settings are passed as a plain PHP array to the Auth constructor, which maps cleanly onto config/saml.php + env vars and survives config:cache. The three logical endpoints (metadata, ACS, SLS) map to three named routes. The main friction points:
OneLogin_Saml2_Utils::getSelfURLNoQuery(), OneLogin\Saml2\Auth). Composer autoloading works, but the container binding, IDE support, and mocking story is worse than a modern namespaced package. We should wrap it behind our own SamlProvider interface from day one rather than calling Auth directly from controllers.InResponseTo validation live in the session. On multi-node deployments without a shared session store this breaks silently between the redirect and the POST-back.certs/ and settings.php defaults. The README explicitly warns that files placed under vendor/onelogin/php-saml are destroyed by composer update. Certificates must live in storage/app/saml (or a secrets manager), never in vendor or config/.VerifyCsrfToken::$except pre-Laravel 11, validateCsrfTokens()->except() in Laravel 11/12). This is a one-line change but is a common source of "works locally, 419 in staging" incidents.Technical risk. Moderate, and concentrated in security configuration rather than the library itself.
strict: true, non-SHA-1 signatureAlgorithm/digestAlgorithm, and IdP certificate registration rather than fingerprint comparison. Any of these left at defaults is a production vulnerability that will not surface in functional testing. This needs a config validation test in CI, not a code review.rejectUnsolicitedResponsesWithInResponseTo and destinationStrictlyMatches default to disabled and must be turned on deliberately. Replay protection is explicitly not provided — the library only exposes getLastMessageId() / getLastAssertionId() so we can implement a cache-backed dedupe. That is our control to build and operate.xmlseclibs carries the real CVE history (CVE-2019-3465, CVE-2016-1000253 signature wrapping, and the most recent CVE-2025-66475 patched in 2.21.1). Our exposure is therefore driven by a transitive package's release cadence as much as this one's.Key questions to resolve before committing:
rejectUnsolicitedResponsesWithInResponseTo, which materially weakens replay posture and requires an alternative control.Verdict: sound choice. It is the de facto standard PHP SAML toolkit and the alternative (writing SAML validation ourselves, or adopting SimpleSAMLphp as a separate service) is worse on both risk and effort. Approve, but budget explicitly for the wrapper layer and security hardening rather than treating it as a drop-in package.
Stack fit. Requires PHP ≥ 7.3 on the 4.x branch (PHP 8.x supported), which is satisfied by any Laravel 9+ application. It has no Laravel-version coupling whatsoever, so it will not block framework upgrades — a meaningful advantage over the abandoned aacotroneo/laravel-saml2 wrapper, which has historically lagged both Laravel and php-saml releases. Install with composer require onelogin/php-saml:^4.0 and pin to a caret range, not dev-master.
Recommended shape. Keep the vendor library at arm's length:
config/saml.php — env-driven, one array per IdP (or per tenant), including strict, algorithms, certs, and endpoint URLs. Must be compatible with config:cache (no closures, no runtime lookups).app/Services/Saml/SamlProviderFactory.php — builds a configured OneLogin\Saml2\Auth from a settings array; the only place in the codebase that instantiates vendor classes. Bind to an interface so tests can fake it.app/Http/Controllers/SamlController.php — login, acs, sls, metadata actions, each thin: delegate to the provider, translate results into Laravel auth events.app/Services/Saml/SamlUserResolver.php — maps NameID + attributes to an Eloquent user, applies JIT provisioning or rejects, then Auth::login($user, remember: false) and session()->regenerate().app/Services/Saml/ReplayGuard.php — cache-backed set of processed message/assertion IDs keyed with TTL derived from NotOnOrAfter, using the shared cache store (Redis), not the array driver.Migration path. If a SAML integration already exists via aacotroneo/laravel-saml2 or 24slides/laravel-saml2, both are wrappers over this same library, so the migration is largely a de-wrapping exercise: lift the settings array out of the wrapper's config, move certificate paths into storage, and replace the wrapper's routes with our own controller. User records and NameID mappings carry over unchanged. If migrating from a Socialite-based OIDC flow, there is no shortcut — SAML is a different protocol and the flows do not share code.
Compatibility considerations.
Auth per request (cost is negligible) rather than resolving it as a singleton.getSelfURLNoQuery() derives the ACS URL from request headers. Behind a proxy, TrustProxies must be configured correctly or Destination validation will fail. Prefer hardcoding sp.assertionConsumerService.url in settings over URL auto-detection.post_max_size before go-live, not during the customer's first login attempt.openssl (the old mcrypt requirement is legacy 2.x guidance and should be ignored); confirm the SP private key is available to the app at runtime and is not committed.Sequencing.
config/saml.php.Realistic estimate for a single-IdP, single-tenant integration with JIT provisioning: 1–2 weeks for a mid-level Laravel developer, assuming IdP test credentials are available on day one. Multi-tenant plus SLO: 4–6 weeks. The dominant schedule risk is IdP-side configuration turnaround with customer IT, not our engineering.
Maintenance. Low-to-moderate, but with a hard floor: security patch tracking is mandatory, not discretionary. Our update trigger is twofold — this package's releases and, more often, xmlseclibs advisories. Add Dependabot/Renovate with auto-PR for patch versions, a monthly review cadence, and a named owner. Because the release cadence is event-driven and the maintainer is essentially one person, we should be prepared to patch by pinning a specific commit if a CVE lands and a tagged release lags. Keep the wrapper interface thin enough that swapping the protocol engine is a contained change, not a rewrite.
Support. No commercial support tier, no SLA. Community support is via GitHub issues. For an enterprise-facing SSO feature this means we are the tier-1 and tier-2 support for SAML — and SAML failures arrive as "I can't log in," which is a P1 by default. Budget for:
Scaling. The library is not a scaling concern. Signature verification is a single RSA operation per login — sub-10ms — and SSO traffic is bounded by human login rates, not API volume. No horizontal scaling issues, no state beyond the session and the replay cache. The two scaling-adjacent items:
curl and must never happen on the request path. Pull metadata on a scheduled job, diff it, alert on certificate or endpoint changes, and require human approval to apply — silent auto-apply of a changed IdP certificate defeats the trust model.Failure modes to design and alert for.
sp_new.crt mechanism the toolkit supports.NotBefore/NotOnOrAfter validation fails if NTP drifts. Alert on host clock offset; know the allowed skew in the settings.SameSite policy on the IdP-initiated round trip produce a generic failure. Needs explicit error messaging.rejectUnsolicitedResponsesWithInResponseTo is on, those users fail with no obvious cause. This must be a per-IdP setting, not a global one.Ramp-up. The library's API is learnable in a day; SAML itself is not. The real ramp-up cost is protocol literacy: AuthnRequest/Response/Assertion structure, HTTP-Redirect vs HTTP-POST bindings, what "signed assertion but unsigned response" implies, and why Destination and Audience validation matter. A developer new to SAML should be given the spike task with a real IdP tenant, not a mocked one. Plan 3–5 days of genuine learning curve on top of the integration estimate, and ensure at least two engineers touch the ACS path so this is not a single-person knowledge silo. Onboarding a new IdP once the wrapper exists should drop to a few hours of configuration, which is the metric to hold the design to.
How can I help you explore Laravel packages today?