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

Onelogin Saml Bundle Laravel Package

nbgrp/onelogin-saml-bundle

Symfony bundle integrating the OneLogin PHP SAML toolkit for easy SAML service provider setup, SSO authentication, and wiring SAML logins into Symfony’s security system.

View on GitHub
Deep Wiki
Context7

Getting Started

What it is. A thin, Symfony-native wrapper around onelogin/php-saml: it turns per-provider YAML into OneLogin Settings, registers SamlAuth/SamlAuthFactory services, exposes an SP metadata route, and plugs into the Security component via a user provider and a custom authenticator. Multi-provider (multiple IdPs) is first-class — most "simple" SAML bundles are not.

Install.

composer require nbgrp/onelogin-saml-bundle

There is no Flex recipe, so register it manually in config/bundles.php (Nbgrp\OneloginSamlBundle\NbgrpOneloginSamlBundle::class => ['all' => true]) and create config/packages/nbgrp_onelogin_saml.yaml.

Version note (v3.0.0). v3.0.0 adds Symfony 8.0 support behind a major version bump. The bundle tracks Symfony's own support window, so a major version almost always means older Symfony minors were dropped, not just that 8.0 was added — read composer.json (require for symfony/* and php) before upgrading. Practically: if you are on Symfony 5.4/6.x, stay on the 2.x line; if you are planning a Symfony 7 → 8 upgrade, wait until the app itself is on 8 and then take v3 in the same change, so you only debug the bundle once. Pin the constraint (^3.0) rather than floating, and re-run php bin/console debug:container nbgrp_onelogin_saml after the upgrade to confirm the service IDs you autowire are unchanged.

Where to look first.

  1. config/packages/nbgrp_onelogin_saml.yaml — one providers entry per IdP, default_provider picks the one the firewall uses.
  2. security.yaml — a user provider + custom_authenticator.
  3. Your User entity — and either the built-in attribute mapper or your own SamlUserFactoryInterface.
  4. php bin/console debug:config nbgrp_onelogin_saml and debug:router | grep saml to see the merged config and the metadata/ACS routes.

Minimal config.

# config/packages/nbgrp_onelogin_saml.yaml
nbgrp_onelogin_saml:
    default_provider: corporate
    providers:
        corporate:
            idp:
                entityId: '%env(SAML_IDP_ENTITY_ID)%'
                singleSignOnService:
                    url: '%env(SAML_IDP_SSO_URL)%'
                singleLogoutService:
                    url: '%env(SAML_IDP_SLO_URL)%'
                x509cert: '%env(base64:SAML_IDP_CERT)%'
            sp:
                entityId: '%env(SAML_SP_ENTITY_ID)%'
                assertionConsumerService:
                    url: '%env(SAML_SP_ACS_URL)%'
            security:
                authnRequestsSigned: false
                wantAssertionsSigned: true
                wantMessagesSigned: true
# config/packages/security.yaml
security:
    providers:
        saml_users:
            id: nbgrp_onelogin_saml.user_provider
    firewalls:
        saml:
            pattern: ^/(saml|login)
            provider: saml_users
            custom_authenticators:
                - nbgrp_onelogin_saml.authenticator
            entry_point: App\Security\SamlEntryPoint   # or your login controller
            logout:
                path: app_logout
    access_control:
        - { path: ^/saml/(metadata|acs), roles: PUBLIC_ACCESS }
        - { path: ^/, roles: ROLE_USER }

First use case: SP-initiated SSO. Point your "Log in with SSO" button at a controller that calls SamlAuth::login(). php-saml builds the AuthnRequest, redirects the browser to the IdP, and the IdP POSTs the assertion back to your ACS URL; the bundle's authenticator validates it, loads/creates the user, and fires the normal Symfony login events. Give the IdP your metadata URL (nbgrp_onelogin_saml_metadata route) so it knows your entity ID, ACS URL, and signing certificate. Nothing works until those three values match on both sides exactly — do that handshake before debugging anything else.

Implementation Patterns

Login controller. login() performs the HTTP redirect internally (php-saml calls exit), so don't expect a return value to wrap:

#[Route('/saml/login', name: 'saml_login')]
public function login(#[Autowire(service: 'nbgrp_onelogin_saml.auth')] SamlAuthInterface $auth): Response
{
    $auth->login($this->getParameter('saml_return_to')); // RelayState
    return new Response(status: 204); // unreachable in practice
}

Autowire by service ID (nbgrp_onelogin_saml.auth, ...auth_factory, ...user_provider, ...authenticator) rather than FQCN so minor namespace moves between versions don't break you. This matters more than usual across the 2.x → 3.x boundary: service IDs are stable, class namespaces are not guaranteed.

Deep links / target path. Unauthenticated requests hit your AuthenticationEntryPoint; have it redirect to /saml/login?_target_path=..., store the target in the session, and after the authenticator succeeds redirect there. If you prefer RelayState, pass the URL to login($returnTo) and check that your version's authenticator honors it as the target — some do, some leave it to you.

Attribute mapping. SAML attributes arrive as arrays keyed by whatever the IdP sends — friendly names (mail, memberOf) or OIDs (urn:oid:0.9.2342.19200300.100.1.3). Two clean options:

  • Use the built-in AttributeMapper (configure the user entity class plus a username/email/roles map) and the shipped SamlUserProvider.
  • Implement SamlUserFactoryInterface::createUser(string $identifier, array $attributes): SamlUserInterface when you need real hydration logic, and inject it into nbgrp_onelogin_saml.user_provider.

In both cases, normalise: is_array($v) ? $v[0] : $v, strip group prefixes (CN=...), and always apply default roles.

Event listener for post-login logic. Subscribe to SamlUserEvent to tweak the user or capture data the mapper doesn't handle. This is also the natural place to persist getSessionIndex() (e.g., on the user row or in the session) — you need it later for single logout.

Multiple IdPs. Define each provider, then use nbgrp_onelogin_saml.auth_factory to get an auth instance for a specific name:

$auth = $authFactory->createAuth($request->attributes->get('idp')); // 'corporate', 'partner', ...
$auth->login();

If the built-in authenticator resolves only the default provider on ACS, the reliable pattern for genuinely multi-tenant SSO is one firewall per IdP (or a thin custom authenticator delegating to the right SamlAuth). Verify against the installed version before designing around it.

Single logout. Symfony's default logout just kills the local session; the IdP is never told. Add a LogoutSuccessHandler (or dedicated controller) that calls SamlAuth::logout($returnUrl) with the stored session index, and expose a public route for the IdP-initiated SLO callback. Order matters: read sessionIndex/NameID before invalidate_session.

Testing. Functional tests should use $client->loginUser($user) and skip SAML entirely. Unit-test the attribute mapper/factory with fixture attribute arrays — that's where the real logic lives. For the ACS path itself, mock the SamlAuthInterface service rather than trying to forge signed assertions.

Gotchas and Tips

  • Upgrading to 3.x is not a no-op. The changelog reads as a one-liner ("Symfony 8.0 support"), but the major version means the Symfony/PHP constraints moved. Read the diff of composer.json on the v3.0.0 tag, run composer why-not if resolution fails, and diff your merged config (debug:config) before and after — YAML keys that were silently ignored on 2.x can start failing on 3.x. Do the bundle bump in the same commit as the framework bump, never ahead of it.
  • Pinning across majors. If you use nbgrp_onelogin_saml.* service IDs directly (you should), a constraint of ^2.1 || ^3.0 lets Composer pick 3.x on a Symfony 7 app during an unrelated composer update. Pin to the line that matches your Symfony version.
  • Entity ID / ACS URL must match byte-for-byte. Trailing slash, http vs https, port, and hostname all count. Behind a load balancer, configure trusted_proxies/X-Forwarded-* or the generated ACS URL and OneLogin's currentURL check will disagree.
  • Certificates in env vars are painful. PEM newlines get mangled. Use %env(base64:SAML_IDP_CERT)% or file:. OneLogin accepts raw base64 or full PEM.
  • Session cookie SameSite. The IdP's POST to your ACS is a cross-site POST; SameSite=Lax cookies are not sent, except in Chrome's 2-minute "Lax+POST" grace window. That makes logins work intermittently. Use SameSite=None; Secure (or ensure a fresh session) for the SAML firewall. Newer Symfony versions default samesite to lax, so this bites on fresh installs as well as upgrades.
  • php-saml is session-based. It stores request IDs and NameID in the session to validate InResponseTo. Stateless firewalls, a wiped session, or a cookie set after the ACS POST produce "Invalid SAML Response" with no obvious cause. Keep stateless: false.
  • debug: true in production leaks assertions into logs/errors. Gate it on the environment.
  • NameID format. Transient NameIDs change every login — never use them as a DB key. Persistent NameIDs need the bundle's persistent storage or your own. Identifier from an attribute (uid, mail) is the least surprising.
  • Clock skew. NotBefore/NotOnOrAfter failures are almost always a drifting container clock, not a config bug. Ensure NTP and check getLastErrorReason().
  • Encrypted assertions need sp.privateKey and your SP certificate registered at the IdP. Mismatched pairs fail as "Unable to decrypt assertion".
  • Authenticator not firing. It resolves the ACS request by matching the configured URL/route; if your route is rewritten or the proxy changes the URL, supports() returns false and you get a 404 or a form-login fallback instead of a SAML error.
  • No entry_point = 401, not a redirect. With only a custom_authenticator, unauthenticated users get an unauthorized response rather than being sent to the IdP. Provide an entry point.
  • Don't add CSRF or auth to /saml/acs or /saml/metadata. The assertion is the credential; CSRF or firewall rules on those paths break the flow.
  • Debug with the raw payload. getLastResponse(), getLastRequestXML(), getLastMessageId() and getLastErrorReason() are your friends — log them in dev. php bin/console debug:container nbgrp_onelogin_saml confirms wiring.
  • Extension points are the attribute mapper/factory, SamlUserEvent, a custom authenticator, and overriding the metadata controller. Prefer these over forking; the bundle is small and actively released.
  • Certificate rotation. Publish new SP metadata before swapping keys and keep the old cert trusted at the IdP during overlap — IdPs cache metadata aggressively. Worth scheduling during a framework upgrade anyway, since you'll be re-testing the full IdP round trip regardless.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager