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

Sso Sp Bundle Laravel Package

korotovsky/sso-sp-bundle

Symfony bundle that adds SAML 2.0 Service Provider support, letting your application authenticate users through an external identity provider. Configure metadata, routes, and user mapping to integrate corporate or cloud IdPs with minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

First, verify the premise. Despite the triage label, this is a Symfony 2 bundle, not a Laravel package. Before you write a single line, run:

composer require korotovsky/sso-sp-bundle

In a Laravel app on PHP 8.1+ this will fail or drag in symfony/http-kernel, symfony/event-dispatcher, and symfony/security at 2.x versions that conflict with the framework's own constraints. Treat a clean composer require as the exception, not the rule. If it does resolve, you almost certainly have an older Laravel and older PHP, and the rest of this document still applies.

Where to look first. Read the repository in this order — it maps to the four things you'll re-implement:

  1. DependencyInjection/ + Resources/config/services.yml — tells you the full list of services and their wiring, i.e. the real API surface.
  2. The firewall factory / listener under Security/ (or EventListener/) — this is where the actual auth handshake lives: it intercepts the request, checks for an SP session, and redirects or validates.
  3. The controller behind the login/callback/logout routes — the callback method is where the IdP response is parsed and the user is resolved.
  4. The user provider / mapper — how an SSO profile is turned into a local identity.

First use case. Get the callback path working end-to-end against a real IdP sandbox before touching login or logout. That single flow exercises the hard parts: metadata, redirect URL construction, response parsing, signature validation, attribute mapping, and session establishment. If the callback is solid, the rest is plumbing.

Minimal wiring you'll need in Laravel. There is no service provider to publish from — you build the shell yourself:

// config/sso.php — mirror the bundle's config keys
return [
    'idp' => [
        'entity_id'   => env('SSO_IDP_ENTITY_ID'),
        'sso_url'     => env('SSO_IDP_SSO_URL'),
        'slo_url'     => env('SSO_IDP_SLO_URL'),
        'certificate' => env('SSO_IDP_CERT'), // or a path
    ],
    'sp' => [
        'entity_id'    => env('SSO_SP_ENTITY_ID'),
        'acs_url'      => env('APP_URL') . '/sso/callback',
    ],
    'routes' => [
        'login'    => 'sso/login',
        'callback' => 'sso/callback',
        'logout'   => 'sso/logout',
    ],
    'middleware' => ['web'],
    'user' => [
        'model'      => App\Models\User::class,
        'identifier' => 'email',   // which attribute maps to your unique key
        'map'        => ['name' => 'displayName', 'email' => 'mail'],
    ],
];

Then a service provider that binds a protocol client, registers routes, and a guard that reads the SSO session. The bundle's services.yml is your checklist for which of those bindings need to exist.


Implementation Patterns

Pattern 1: Extract the protocol layer, keep Laravel idioms

The bundle's value is a thin orchestration layer around a SAML toolkit. That orchestration is what you port; the protocol work stays in the underlying library. Wrap it behind one interface so the rest of the app doesn't care:

interface SamlClient
{
    public function getLoginUrl(string $returnTo): string;
    public function parseResponse(string $samlResponse): SamlAssertion;
    public function getLogoutUrl(string $returnTo): string;
    /** @return array<string,string> */ public function metadata(): array;
}

Implement it against whatever the bundle wraps (OneLogin php-saml, LightSAML, robrichards/xmlseclibs). Confirm this before committing to a build — that library's maintenance status decides whether the approach is salvageable at all.

Pattern 2: Custom guard + middleware, not a firewall listener

The bundle hooks Symfony's security firewall via an event listener. Laravel's equivalent is a guard + middleware. Two clean seams:

  • Middleware (EnsureSsoSession) — on protected routes, checks for a hydrated SSO session; if absent, redirects to route('sso.login') with the intended URL stored in session (the bundle's "redirect after login" config maps to this).
  • Guard (SsoGuard) — user(), check(), id() read from the session and resolve via the user provider. login()/logout() are handled by your callback and logout controllers, not the guard.
class SsoGuard implements Guard
{
    public function user()
    {
        if ($this->user !== null) return $this->user;
        $id = $this->session->get('sso.user_id');
        if ($id === null) return null;
        return $this->user = $this->provider->retrieveById($id);
    }

    public function validate(array $credentials = []) { return $this->user() !== null; }
    public function check() { return $this->user() !== null; }
    // ...logout clears the SSO session keys the callback set
}

Register it in config/auth.php under a dedicated driver. Keeping it a driver means Auth::user() works everywhere unchanged — the same guarantee the bundle gives Symfony apps through security.context.

Pattern 3: Just-in-time user provisioning with an explicit mapper

The callback should be the only place that creates users. Map the assertion to a local account in one testable unit:

class SsoUserProvisioner
{
    public function __construct(private array $config) {}

    public function provision(SamlAssertion $a): User
    {
        $key = $this->config['user']['identifier'];
        $id  = $a->get($this->config['user']['map'][$key] ?? $key);

        return User::updateOrCreate(
            [$key => $id],
            $this->mapAttributes($a),  // name, email, and any role claims
        );
    }
}

Two rules the bundle gets right and you should copy: the identifier field must be stable across IdP renames (prefer the immutable subject/NameID over email if your IdP provides one), and role/entitlement claims must be re-synced on every login, not just at creation — otherwise IdP-side role changes never propagate.

Pattern 4: Routes and config as first-class, not hardcoded

Keep the bundle's strength — drop-in login/callback/logout routes with configurable middleware — by registering them from the service provider:

Route::middleware(config('sso.middleware'))
    ->prefix(config('sso.routes.prefix', 'sso'))
    ->group(function () {
        Route::get('login',    [SsoController::class, 'login'])->name('sso.login');
        Route::match(['get','post'], 'callback', [SsoController::class, 'callback'])->name('sso.callback');
        Route::get('logout',   [SsoController::class, 'logout'])->name('sso.logout');
    });

Note the callback accepts POST — IdP-initiated flows post the assertion to the ACS; SP-initiated redirect flows use GET. The bundle supports both; make sure yours does too if you expect IdP-initiated logins.

Pattern 5: Session handling that survives logout

Laravel's session is fine, but SSO sessions need explicit lifecycle management:

  • On successful callback: Auth::login($user), regenerate the session, and store any SSO-specific tokens/NameID under your own keys.
  • On logout: you have local logout (clear the Laravel session) and single logout (redirect to the IdP's SLO endpoint, then expect a back-channel or front-channel logout). Do the local one unconditionally; treat SLO as best-effort.

Gotchas and Tips

The framework mismatch is the headline. Symfony 2's EOL was Nov 2018. The bundle's Composer constraints will target PHP 5.x/7.0-era runtimes and Symfony 2.x. There is no install-and-configure path in a modern Laravel app. The realistic modes are (a) read it as a reference and re-implement, or (b) vendor a forked copy and own it. Do not expect to track upstream releases — there's no cadence to track.

Security is the actual risk, not the integration friction. An SP is where SAML implementations fail catastrophically. Verify against the repository and the underlying toolkit before trusting anything:

  • Are signatures validated on the assertion, the response, or both? Accepting an unsigned assertion is the classic signature-wrapping bug.
  • Is Audience, Recipient, and InResponseTo enforced? Missing checks allow assertion replay across SPs.
  • Is XML parsed with external entities disabled (XXE) and signature verification done on the original bytes?

Zero stars and zero dependents means no crowd-sourced review of this logic. If you lift code, put a negative-path test suite in CI: unsigned assertion, replayed assertion, wrong audience, expired assertion. The failure mode is silent acceptance, not a crash — which is why reviewers miss it.

Certificate expiry and rotation is the #1 operational outage. The IdP's signing cert rolls, nobody updates SP metadata, every login fails at once. Build an expiry alert and a rehearsed rotation runbook before go-live. The bundle gives you no tooling for this.

Clock skew breaks NotBefore/NotOnOrAfter and produces maddening intermittent failures. Enforce NTP on app servers and surface the specific validation error (expired vs not-yet-valid vs skew) instead of a generic "SSO failed." The bundle tends to collapse errors into one redirect — don't inherit that.

Config quirk: endpoint URL vs metadata URL. The bundle exposes both an explicit IdP endpoint and metadata parsing paths. Metadata parsing is convenient but caches; endpoint config is explicit but you own rotation. Pick one deliberately — mixing them means metadata refreshes silently override your endpoint config and you spend an afternoon debugging why the login URL "changed itself."

PSR-0 to PSR-4 and DI to container bindings. Any fork needs namespace modernization and the services.yml config rewritten as Laravel bindings. Budget real time for this — it's mechanical but not small, and it's where subtle wiring bugs hide.

Logout inconsistency. Single Logout is unreliable across IdP implementations. If you advertise SLO and it half-works, you get sessions that outlive IdP logout — a real security concern. Prefer short session lifetimes plus aggressive re-validation over trusting SLO to be complete. Test logout explicitly against your IdP; don't assume the bundle's logout route does what the IdP expects.

Debugging SSO is opaque by nature. The bundle sends users to the IdP and receives base64 SAML back — there's no stack trace in the browser. Keep a debug mode that logs the decoded SAML response (to a secure, access-controlled log — never default app logs, it contains PII and tokens), and validate against an IdP sandbox, not production, until the callback is green.

Extension points worth keeping from the bundle. The user mapper, route/middleware config, and redirect-after-login are the pieces you'll actually customize. Keep them as interfaces/config, not edits — that's the bundle's one genuinely good design choice and it's cheap to preserve in the port.

Attribute drift is worse than an outage. If the IdP changes an attribute name, the user authenticates successfully but lands with missing roles — a silent authorization bug. Pin the exact attribute names your mapper expects, log a warning when a mapped claim is absent, and treat mapping as a contract to verify whenever IdP config changes on their side.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package