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

Shibboleth Bundle Laravel Package

jonathonwalz/shibboleth-bundle

A Symfony bundle for integrating Shibboleth single sign-on into your application. It maps Shibboleth session attributes to Symfony security users, enabling SSO authentication and logout workflows with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

First, settle what "leverage" means here. jonathonwalz/shibboleth-bundle is a Symfony 2 bundle. It will not install into a Laravel app — there is no bundle registry, no container compiler pass, and its Composer constraints (Symfony 2.x, PHP 5.x/7.0-era) will not resolve against Laravel 10/11 on PHP 8.1+. Do not spend a day fighting composer require; it cannot succeed and the resulting tree would be unmaintained auth code. You are adopting the pattern — trust-the-SP, read headers, map attributes, provision locally — and writing roughly 150–250 lines of Laravel to do it.

Where to look first in the repository. Read in this order, and read for behaviour, not for code you can copy:

  1. The security factory / authentication provider — this shows the exact set of Shibboleth headers the author trusted (Shib-Session-Id, Shib-Identity-Provider, eppn, mail, displayName, affiliation, persistent-id) and the order of checks.
  2. The user provider — this is the attribute-to-user mapping and the Just-In-Time provisioning logic. It is the genuinely reusable part.
  3. The firewall / listener config example — this tells you where in the request lifecycle the check fires and what a "not behind the SP" rejection looks like.
  4. The logout handler — usually the thinnest part, and usually where the SLO gap hides.

The minimal viable Laravel equivalent — the first use case. One middleware that refuses any request arriving without an SP session marker, plus a small service that resolves or creates a User from the headers:

// app/Http/Middleware/TrustShibbolethHeaders.php
public function handle(Request $request, Closure $next)
{
    abort_unless($this->fromTrustedProxy($request), 403);
    abort_unless($request->server('Shib-Session-Id'), 401); // not authenticated at the SP

    return $next($request);
}

Prerequisites you cannot skip, and which gate everything else:

  • An operational Shibboleth SP (mod_shib on Apache, or an equivalent reverse proxy) sitting in front of every node. Without this, headers are forgeable and the whole model collapses.
  • Confirmed attribute release from the campus IdP — not what the federation docs claim they release, but what they actually release to your entity ID. Get this in writing/staging traffic before writing mapping code.
  • A decision on the authoritative identifier: eppn (reassignable at some institutions), persistent-id (per-SP, stable), or pairwise-id (pseudonymous). This decision determines your account-linking schema, so make it before your first migration.

Your first shippable slice is deliberately narrow: a staging SP, a route protected by the middleware above, a hard-coded attribute dump to a log, and a manually created user. Prove the headers arrive with the values you expect on the production SAPI. Only then write provisioning.

Implementation Patterns

Pattern 1 — middleware gate, then resolve. Keep the "is this request from the SP?" check in middleware and the attribute mapping in a dedicated service. Mixing them makes the security check impossible to audit and the mapping impossible to unit test.

// app/Services/ShibbolethAttributeMapper.php
public function map(array $attributes): array
{
    return [
        'shibboleth_id' => $attributes['persistent-id'] ?? $attributes['eppn'],
        'name'          => $attributes['displayName'] ?? $attributes['cn'] ?? 'Unknown',
        'email'         => $attributes['mail'] ?? null,
        'affiliation'   => $this->parseAffiliations($attributes['affiliation'] ?? ''),
    ];
}

Pattern 2 — JIT provisioning behind an interface. Laravel's Auth::login() plus a firstOrCreate-style resolver is the direct analogue of the Symfony user provider. Resolve by the immutable identifier, never by email (emails change and are frequently reassigned):

$user = User::firstOrCreate(
    ['shibboleth_id' => $attrs['shibboleth_id']],
    ['name' => $attrs['name'], 'email' => $attrs['email']]
);

Auth::login($user, remember: false);
$request->session()->regenerate();

Call session()->regenerate() explicitly — Shibboleth auth is a session-fixation-adjacent surface and Laravel only regenerates on its own login paths.

Pattern 3 — a custom guard if you need Auth::guard('shibboleth'). Wrap the request-time resolution in a Guard implementation so it composes with existing auth middleware, policies, and Blade @auth directives rather than bypassing them. Bind it in a service provider via Auth::extend(). In practice most teams are fine with middleware + Auth::login(), and it's the lower-risk option; the custom guard earns its keep only when you have mixed authentication (some users via password, some via institution) on the same routes.

Pattern 4 — role mapping from affiliation / entitlement. Institutions emit affiliation=staff;student;member and increasingly entitlement URNs from REFEDS. Map these to local roles on login, not on every request, and treat them as advisory: an IdP is authoritative about affiliation, not about what your app permits. Persist the raw attribute set on the user record (a JSON column) so you can re-derive mappings later without asking the IdP team for another release.

Pattern 5 — dual-run during cutover. Add the Shibboleth guard alongside your existing auth for one release cycle. Users who already have local accounts need a linking flow (log in locally once, then link by clicking through the SP). Shibboleth's opaque identifiers give you no automatic way to match an existing row — plan the linking UI; it is the part teams forget and it generates the most support tickets.

Pattern 6 — logout. Your app logout must destroy the Laravel session, then redirect the user to the SP's logout endpoint (/Shibboleth.sso/Logout) to clear the IdP session. Skipping the second step means "log out" appears to fail — the user is immediately re-authenticated. If the IdP supports SLO, wire it; if not, at minimum redirect to the SP logout so the local SP session is dropped, and document the residual gap.

Pattern 7 — an SP shim you can develop against. Locally you have no SP. Write a middleware (or a php artisan shibboleth:simulate command setting session state) that injects synthetic headers in local/testing only, gated on app()->environment(['local','testing']). This is what makes the whole thing testable in CI and keeps developers from disabling auth to work on features.

Testing pattern. Feature-test the mapper and guard with synthetic attribute arrays: missing eppn, changed identifier, empty affiliation, unexpected extra attributes, two users with the same mail. Assert that a request with headers but from an untrusted source is rejected. Assert that a second login with a reissued identifier does not create a duplicate user.

Gotchas and Tips

Header spoofing is the failure mode that matters. The package trusts eppn from the environment. If any path reaches your app without traversing the SP — a direct-to-origin IP, a misconfigured load balancer health check, a container ingress, a developer's php artisan serve on a public interface — anyone can curl -H 'eppn: [email protected]' and become that user. Enforce this at the network layer (security group or WAF that only accepts the SP's source, or mod_shib with a ShibRequestSetting requireSession 1), and also assert it in code with a trusted-proxy allowlist. Defence in depth, because a network rule is one Terraform PR away from gone. This belongs in your pen-test scope explicitly.

Never trust headers on a non-SP path. Health checks, /api webhooks, and static asset routes often bypass the SP. Mark protected routes explicitly; don't assume a global middleware covers everything if the SP sits in front of only some vhosts.

SAPI variance will bite you during rollout. mod_php and PHP-FPM expose Shibboleth attributes differently ($_SERVER vs. getenv()), and php-fpm requires fastcgi_param passthrough configured per attribute or you get silent nulls. Verify on the exact production runtime, in a container that mirrors it, before trusting your mapping. This is the single most common "works in staging, blank names in prod" cause.

Config caching. Laravel's php artisan config:cache will happily freeze any Shibboleth-related config. Keep attribute names and the trusted-proxy list in config/shibboleth.php and never read env() outside config files, or you'll ship an app that ignores the environment.

Attribute drift is silent. The IdP team can change an attribute name, scope an eppn differently, or stop releasing a field, with no notice to you. Log the raw attribute set at login (redacting nothing is a privacy question — log the keys and a hash of values, or gate full logging behind a flag) and alert on authentication failure rate. A sudden drop from 99% to 40% success is your only signal.

eppn is not always stable. Some institutions reassign eppn when a person leaves and someone else arrives. If you link accounts by eppn, you will eventually merge two humans. Prefer persistent-id (scoped to your entity ID, stable, opaque) and store eppn as a display/search field only.

persistent-id is per-SP — treat it as a secret. It is not portable across entity IDs, so if you re-register the SP or change entity ID, every user's identifier changes and you face a mass re-linking event. Record the entity ID in your runbook and treat it as immutable.

Certificate and metadata expiry. SP and IdP signing certificates rotate; metadata has a validUntil. Neither failure is graceful — logins just stop. Put both expiries in monitoring with 30-day warnings, and know who at the IdP renews theirs.

Clock skew. Assertion validity is time-bounded; NTP is load-bearing infrastructure you don't control. If auth works intermittently on one node, check its clock first.

Multi-node SP session cache. mod_shib maintains its own session cache. Behind a load balancer without sticky sessions or a shared cache backend, users re-authenticate randomly as they bounce between nodes. This looks like an application bug and is not one — it will cost you a day if you haven't planned for it.

De-provisioning is a real requirement, not an edge case. When a person leaves the institution, the IdP stops asserting for them — but your local User row and any active Laravel session persist until expiry. Short session lifetimes plus an explicit "re-verify affiliation on login" check are the pragmatic mitigation; full de-provisioning needs a reconciliation job against your user store.

Config quirks in the reference bundle. The bundle's firewall config assumes one IdP per firewall and one attribute mapping globally. If you need per-institution behaviour (different attribute names across federations — eduPersonPrincipalName scoping varies), build your mapper as a strategy selected by Shib-Identity-Provider, not as a flat config array. Retrofitting multi-IdP is painful.

Extension points worth copying. The bundle's separation of provider (resolve user), listener (fire on request), and factory (wire config) maps cleanly onto Laravel's service, middleware, and service provider. Keep those three seams so you can swap the SP-trust model for a real SAML library later without rewriting your user logic.

When to abandon the pattern. If the target environment cannot guarantee an SP in front of every request, or you need SP-initiated flows, signed assertion validation, or encrypted assertions, stop and use a maintained SAML library (SimpleSAMLphp, or a maintained Laravel SAML package) or a managed identity vendor. Header trust is the cheapest option only when the network topology is genuinely controlled — and that is an infrastructure commitment, not a code decision.

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.
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels