surfnet/stepup-gssp-bundle
A Symfony bundle for implementing a Generic SAML Step-up Provider (GSSP). It enables applications to request extra authentication, such as a second factor, via SAML, with configurable routes, controllers, and services for integrating Stepup flows into PHP/Symfony projects.
This is a Symfony 6 bundle, not a Laravel package — that distinction shapes everything below. You can read it as "a reference implementation of the GSSP (Generic SAML Step-up Provider) profile" and either (a) run it in a small Symfony sidecar app that handles only the step-up handshake, or (b) mine it for the flow and re-implement the ~3 services in Laravel. Trying to composer require it into a Laravel app will drag in symfony/http-kernel, symfony/dependency-injection, and the bundle registration contract, which Laravel does not satisfy.
Minimal steps if you are standing it up inside Symfony (the only supported path):
composer require surfnet/stepup-gssp-bundleconfig/bundles.php (Surfnet\StepupBundle\SurfnetStepupBundle::class => ['all' => true]).surfnet_stepup.yaml in the bundle's DependencyInjection/Configuration.php) — you need at minimum your own entity ID, the remote IdP metadata, and the LoA levels you accept.@SurfnetStepupBundle/Resources/config/routing.yaml or the attribute-routed controllers) so the step-up endpoints exist.AuthnContextClassRef values you will request — the GSSP profile is fundamentally about asking an IdP for a higher LoA than the one you already have.First use case to target: a single route that requires LoA 2 while the user currently has LoA 1. Send them into the step-up endpoint, let the bundle build the AuthnRequest with the elevated AuthnContextClassRef, and on return resume whatever they were doing. Get that one round trip working before touching anything else.
First places to look in the repo: README.md, DependencyInjection/Configuration.php (the real API surface), the Controller/ directory (the actual endpoints you get), and Service/ (the pieces you would port to Laravel).
The canonical step-up flow. The bundle exists to model this sequence, and every pattern below is a variation on it:
AuthnRequest with the higher AuthnContextClassRef.If you are porting to Laravel, that maps cleanly onto: a StepUp service, a PendingRequest session key, a controller for the ACS endpoint, and a middleware that checks session('loa') >= route_requirement.
Guard-as-middleware. The Symfony equivalent of Laravel middleware is an event listener on kernel.request. Whatever you build, keep the LoA check in one place and have route requirements declared as metadata (attributes or config array) rather than inline if statements. The bundle's flow assumes the guard knows the required LoA and the current LoA; don't scatter that comparison.
Preserve the intended destination. The single most common production bug in step-up flows is losing the user's original URL. Store the intended path in the session before redirecting to the IdP, and restore it on the ACS return — never rely on the IdP to echo it back. Treat this as a first-class concept (PendingStepUp value object with returnTo, requiredLoa, createdAt), not as an ad-hoc session key.
Session continuity across the round trip. The state written before the redirect must be readable after the IdP posts back. In a multi-node deployment that means a shared session store (Redis/DB), not files. In a Laravel context this is your SESSION_DRIVER plus a stable SESSION_COOKIE domain — SameSite settings and the redirect dance are where this breaks.
Testing. The valuable thing to test is the decision logic, not the SAML wire format. Extract "does this request need step-up?" and "did the returned LoA satisfy the requirement?" into pure functions/services and unit-test those. For the ACS endpoint, test with fixture assertions (valid, expired, wrong audience, insufficient LoA) rather than mocking the whole XML-DSig stack.
Integration tip for the sidecar approach. If you run this bundle as a separate Symfony app, put it behind the same cookie domain as your Laravel app and have it redirect back with a signed, short-lived token containing the achieved LoA. Do not have the Symfony app write directly into Laravel's session — that coupling will bite you.
It is not a Laravel package, and pretending otherwise is the biggest pitfall. No service provider, no facades, no config/ publish tag. Budget for either a sidecar app or a manual port. Given the repo has 1 star and 0 dependents, treat it as a well-structured reference, not a dependency.
LoA values are IdP-negotiated, not app-chosen. You request a specific AuthnContextClassRef; the IdP decides what it will actually assert. If your config requests an LoA the IdP does not support, you will get a silent fallback or an error at the IdP, not a nice exception in your app. Verify the exact URNs against your IdP's metadata before writing any code, and log the AuthnContextClassRef actually returned.
Session state and SAML InResponseTo must line up. The bundle correlates the response with the outstanding request ID. If the session is regenerated (login, CSRF rotation, load balancer with sticky-session off), correlation fails and you get an unhelpful "no matching request" error. Check session stability first whenever the ACS endpoint misbehaves.
Configuration is the API. Nearly every behavioural knob lives in the bundle's Configuration.php. Read that file directly rather than the README — the README will lag the tree. Look for the LoA-to-AuthnContextClassRef mapping, the allowed-clock-skew / assertion-validity settings, and the route paths.
Clock skew. SAML assertions have NotBefore/NotOnOrAfter. Default tolerance is usually tight. In containers with drifting clocks you will see intermittent "assertion not yet valid" failures that look random. Ensure NTP and, if exposed, widen tolerance modestly.
Debugging. The failure modes are almost all "redirect happened, nothing useful in the logs." Enable the bundle's logger and log: (1) the outgoing AuthnRequest ID, (2) the required LoA, (3) the returned AuthnContextClassRef, (4) the pending-request lookup result. Those four lines will diagnose the large majority of issues.
Extension points. If you do adopt the bundle, the clean seams are the step-up service (override to change how the return URL is computed), the event dispatcher (hook the pre-redirect and post-ACS points to add audit logging), and the routing config (repoint endpoints under your own firewall). Avoid subclassing controllers — the flow is small enough that forking the logic is safer than overriding half of it.
Maintenance risk. Last release date is far in the future relative to typical project clocks and the project has effectively no downstream users. Pin the version, vendor the parts you rely on if you fork, and keep the SAML library (simplesamlphp/lightsaml, whichever it pins) independently patched — that is where the real security surface lives, not in this bundle.
License is fine. Apache-2.0 means porting the flow into Laravel is legally uncontroversial; keep the NOTICE/attribution if you copy source.
How can I help you explore Laravel packages today?