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

Sp Bundle Laravel Package

lightsaml2/sp-bundle

Symfony bundle integrating LightSAML so your application acts as a SAML 2.0 service provider. Provides DI services, configuration, and routing to handle authn requests, assertions, and metadata exchange with identity providers.

View on GitHub
Deep Wiki
Context7

Getting Started

The hard truth up front: lightsaml2/sp-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/sp_bundle.php that Laravel will publish. If you composer require it into a Laravel app you'll pull in symfony/http-kernel, symfony/dependency-injection, and a bundle bootstrap that never runs — it's dead weight. The asset you actually consume day-to-day is lightsaml/lightsaml, the framework-agnostic core the bundle wraps.

Treat the bundle as documentation for how to wire LightSAML correctly in a framework, not as a dependency.

Minimal steps to first login:

  1. composer require lightsaml/lightsaml — do not require the bundle.
  2. Read two files in the bundle on GitHub first:
    • DependencyInjection/Configuration.php — the canonical config schema (sp, idp, credentials, endpoints).
    • Resources/config/services.yml — the exact LightSAML classes and parameters the bundle wires. This is your blueprint.
  3. Create app/Providers/SamlServiceProvider.php and bind a LightSaml\Store\EntityDescriptor\... + LightSaml\Credential\... graph built from config/saml.php.
  4. Add routes in routes/web.php: /saml/metadata, /saml/login, /saml/acs, optionally /saml/logout.
  5. First use case: SP-initiated HTTP-Redirect SSO. /saml/login builds an AuthnRequest and redirects to the IdP; /saml/acs validates the SAMLResponse and calls Auth::login($user).

Where to look in the core library: LightSaml\Builder\Profile\WebBrowserSso\Sp\SsoSpRedirectProfileBuilder — this is the whole login flow in one class.

Implementation Patterns

Wire the core library into a service provider, not the bundle. Build the profile on demand in a controller (or bind it as a singleton) from your config:

// app/Providers/SamlServiceProvider.php
public function register(): void
{
    $this->app->singleton(SsoSpRedirectProfileBuilder::class, function ($app) {
        $sp = config('saml.sp'); // entityId, acsUrl, cert, key
        $idp = config('saml.idp'); // entityId, ssoUrl, cert

        $spCredential = (new X509Credential(
            (new X509Certificate($sp['cert']))->loadPem(),
            (new PrivateKey($sp['key']))->loadPem()
        ))->setEntityId($sp['entity_id'])->setUsage(CredentialUsage::Signing());

        return new SsoSpRedirectProfileBuilder(
            $sp['entity_id'],
            $idp['sso_url'],
            $sp['acs_url'],
            /* store/context wiring per services.yml */
        );
    });
}

SP-initiated login controller — build message, produce redirect, don't hand-roll XML:

public function login(SsoSpRedirectProfileBuilder $builder)
{
    $profile = $builder->build();
    $httpRequest = $profile->getRequest();               // HttpRedirectBinding
    $context = $profile->getAction()->createActionContext();
    $context->getHttpRequest()->getQueryString();        // or use the message directly

    return redirect()->away($profile->getRequest()->getBinding()->getMessageUrl(...));
}

In practice, use HttpRedirectBinding::send() or construct the redirect URL with SAMLRequest + RelayState query params yourself — the simplest reliable pattern is AuthnRequest → serialize → deflate/base64 → redirect. Keep RelayState to a short opaque token and store the intended destination server-side (session) to close open-redirect holes.

ACS controller — the security-critical endpoint:

public function acs(Request $request, SsoSpRedirectProfileBuilder $builder)
{
    $response = $builder->getResponseContext()->getProtocolResponse(); // parse + validate signatures
    // ^ core parses `SAMLResponse`, verifies XML-DSig against your configured IdP cert
    /** @var Assertion $assertion */
    $assertion = $response->getFirstAssertion();
    $this->assertNotExpired($assertion);
    $this->assertAudience($assertion, config('saml.sp.entity_id'));
    $this->assertInResponseTo($assertion, session('saml_request_id'));

    $attrs = parse_attributes($assertion);
    $user = User::firstOrCreate(
        ['email' => $attrs['mail']],
        ['name' => $attrs['displayName'] ?? $attrs['mail']]
    );

    Auth::login($user);
    $request->session()->regenerate();
    event(new SamlLogin($user, $attrs));

    return redirect()->intended('/dashboard');
}

IdP-initiated is the same ACS, minus the login route and InResponseTo check. You still validate audience, timestamps, and signature. Never skip signature validation for "IdP-initiated is trusted" — that's how wrapping attacks land.

Metadata endpoint — generate from EntityDescriptor + X509Certificate:

public function metadata()
{
    $ed = new EntityDescriptor($this->spEntityId);
    $ed->addSpSsoDescriptor((new SpSsoDescriptor())
        ->addSingleSignOnService(SingleSignOnService::newRedirect($this->acsUrl))
        ->addKeyDescriptor(KeyDescriptor::newSigning(X509Certificate::load($spCert))));
    return response($ed->serializeToXml(), 200, ['Content-Type' => 'application/samlmetadata+xml']);
}

Cache the string — certificates change rarely, and every login shouldn't re-serialize metadata.

User provisioning lives in your ACS controller (or a listener on the SamlLogin event), not in the bundle. That's the "pluggable user provisioning hook" the description refers to — you're the hook.

Firewall/middleware: don't try to bolt SAML into Laravel's auth guard. Use a route middleware EnsureSamlAuthenticated that checks Auth::check() plus a session flag set in ACS, and route to /saml/login otherwise. Keeps the SP flow completely outside Laravel's guard machinery.

Gotchas and Tips

  • Installing the bundle is a trap. composer require lightsaml2/sp-bundle in a Laravel app will either fail resolution (Symfony 6/7 vs. what Laravel expects) or drag in a bundle that never boots. Verify with composer why-not before fighting it. The services.yml is the deliverable, not the dependency.
  • Signature wrapping (XSW) is the failure mode to watch. Ensure you validate the signature against the specific assertion you're reading, not "some signature exists in the document." The LightSAML XML-DSig helpers are decent, but write a test that replays a malicious wrapped-response fixture. This is the one thing that makes SAML adoption risky with a 1-star, 0-dependent wrapper.
  • SAMLResponse is POST form-encoded and often base64-inflated. Laravel's $request->input('SAMLResponse') is fine, but do not run it through csrf — that route must be VerifyCsrfToken::except(['saml/acs']) in app/Http/Middleware/VerifyCsrfToken.php.
  • Clock skew will silently break you. LightSAML checks NotBefore/NotOnOrAfter. Allow ~2 minutes of skew; if you must, override the assertion timestamp validator, but prefer NTP on the boxes. Most "works in stage, fails in prod" SAML bugs are clock or certificate expiry.
  • Certificate rotation is a quarterly on-call event — plan it. Store IdP certificates with expiry dates in config/saml.php, and add a health check / scheduler task that alerts before the cert expires. Nothing else in the bundle does this for you.
  • Config quirks from the Symfony bundle won't publish cleanly into Laravel. The bundle's config uses XML-serialization-friendly structures; you'll want a flat config/saml.php with sp.entity_id, sp.acs_url, sp.cert, sp.key, idp.entity_id, idp.sso_url, idp.cert. Load keys from env('SAML_SP_KEY') with \n escaping handled — multiline PEM in .env is a common footgun; prefer paths via storage_path('saml/sp.key') and file_get_contents.
  • Extension points are what you'd expect from the core, not the bundle: LightSaml\Store\Id\IdStoreInterface (replay protection — back it with Redis or a DB table), LightSaml\Store\Request\RequestStateStore (for InResponseTo validation — back it with the session), and the assertion validators chain. Implement these in Laravel idioms; the bundle's implementations are Symfony-service-shaped and you don't want them.
  • Debugging tip: when signature validation fails with an opaque LightSAML exception, dump $response->getSignature(), $assertion->getSignature(), and the canonicalized signed XML string. 90% of failures are whitespace/canonicalization (C14N) or a mismatched certificate. openssl x509 -in idp.pem -noout -dates first.
  • Low maturity warning: 1 star, 0 dependents, and the LightSAML lineage has a spotty maintenance history. Pin lightsaml/lightsaml to an exact version, run composer audit in CI, and have a fork plan. If this is going into a customer-facing auth path, budget for a security review and expect to be the maintainer of last resort.
  • If you need enterprise SSO on a deadline, weigh the managed broker seriously. The whole point of this package is to save you from writing SAML; but on Laravel, its Symfony wrapper saves you very little, and a managed broker (WorkOS, Auth0) converts a multi-week protocol project into a config change. Spike the core library behind a feature flag for two weeks, and keep a quote in your back pocket.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource