Install the Package
composer require simplesamlphp/saml2:^4.0
Ensure your composer.json targets PHP 8.1+ (Laravel 10/11 compatible).
Set Up Container
Implement SimpleSAML\SAML2\Compat\AbstractContainer to provide external dependencies (e.g., XML parsers, HTTP clients). Example:
// app/Providers/SamlServiceProvider.php
use SimpleSAML\SAML2\Compat\ContainerSingleton;
class SamlServiceProvider extends ServiceProvider
{
public function register()
{
$container = new class implements \SimpleSAML\SAML2\Compat\Container {
public function get($service) {
return match ($service) {
'xml.sec' => new \SimpleSAML\XMLSec\XMLSec(),
'httpclient' => new \GuzzleHttp\Client(),
'randomutils' => new \SimpleSAML\XML\Utils\Random(),
default => throw new \RuntimeException("Unknown service: $service"),
};
}
};
ContainerSingleton::setContainer($container);
}
}
First Use Case: SP-Initiated Login Create a route to trigger SAML authentication:
// routes/web.php
Route::get('/login/saml', [SamlController::class, 'initiateLogin']);
Implement the controller:
// app/Http/Controllers/SamlController.php
use SimpleSAML\SAML2\XML\saml\Issuer;
use SimpleSAML\SAML2\XML\samlp\AuthnRequest;
use SimpleSAML\SAML2\HTTPRedirect;
class SamlController extends Controller
{
public function initiateLogin()
{
$issuer = new Issuer(config('saml.sp.entity_id'));
$randomUtils = new \SimpleSAML\XML\Utils\Random();
$request = new AuthnRequest(
$issuer,
$randomUtils->generateId(),
null,
config('saml.idp.sso_url')
);
$binding = new HTTPRedirect();
$binding->send($request);
}
}
Configure config/saml.php
Define SP/IdP metadata:
return [
'sp' => [
'entity_id' => 'https://your-app.test',
'assertion_consumer_service' => 'https://your-app.test/saml/acs',
'private_key' => file_get_contents(storage_path('saml/sp-key.pem')),
'certificate' => file_get_contents(storage_path('saml/sp-cert.pem')),
],
'idp' => [
'sso_url' => 'https://idp.example.com/saml2/idp/SSOService',
'certificate' => file_get_contents(storage_path('saml/idp-cert.pem')),
],
];
Handle Assertions Create an endpoint to receive SAML responses:
// routes/web.php
Route::post('/saml/acs', [SamlController::class, 'handleAssertion']);
// app/Http/Controllers/SamlController.php
use SimpleSAML\SAML2\XML\samlp\AuthnRequest;
use SimpleSAML\SAML2\XML\saml\Assertion;
public function handleAssertion()
{
$binding = new \SimpleSAML\SAML2\HTTPPost();
$response = $binding->unpack($_POST);
$assertion = Assertion::fromXML($response->getDOMDocument());
$user = $this->mapSamlUserToLaravel($assertion);
auth()->login($user);
return redirect('/dashboard');
}
AuthnRequest, Response).AbstractContainer and manual integration with Laravel’s auth system.$authnRequest = new AuthnRequest(
new Issuer(config('saml.sp.entity_id')),
$randomUtils->generateId(),
null, // NameIDPolicy
config('saml.idp.sso_url'),
null, // Conditions
null, // Scoping
null, // ForceAuthn
null // IsPassive
);
$binding = new HTTPRedirect();
$binding->send($authnRequest);
$binding = new HTTPPost();
$response = $binding->unpack($_POST);
$assertion = Assertion::fromXML($response->getDOMDocument());
$subject = $assertion->getSubject();
$nameId = $subject->getNameID();
$binding = new HTTPRedirect();
$request = $binding->unpack($_GET);
$authnRequest = AuthnRequest::fromXML($request->getDOMDocument());
$issuer = $authnRequest->getIssuer();
$assertion = new Assertion(
new Subject(
new NameID('user@example.com', 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress')
),
$authnRequest->getAssertionConsumerServiceURL(),
$authnRequest->getIssueInstant(),
$authnRequest->getID()
);
$response = new Response($assertion, $authnRequest->getDestination());
$binding = new HTTPPost();
$binding->send($response);
$metadata = new \SimpleSAML\SAML2\Metadata\SPSSODescriptor(
new EntityDescriptor(
new EntityID('https://your-app.test'),
new KeyDescriptor([new X509Certificate(config('saml.sp.certificate'))]),
new NameIDFormat('urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress')
),
'https://your-app.test/saml/acs',
'https://your-app.test/saml/slo'
);
$metadata->generateMetadataFile(storage_path('saml/sp-metadata.xml'));
public function mapSamlUserToLaravel(Assertion $assertion)
{
$attributes = $assertion->getAttributeStatement()->getAttributes();
$email = $attributes['email'][0] ?? null;
$groups = $attributes['groups'] ?? [];
return User::firstOrCreate(
['email' => $email],
['name' => $email, 'groups' => json_encode($groups)]
);
}
Create middleware to validate SAML sessions:
// app/Http/Middleware/ValidateSamlSession.php
use SimpleSAML\SAML2\XML\saml\Assertion;
class ValidateSamlSession
{
public function handle($request, Closure $next)
{
if ($request->has('SAMLResponse')) {
$binding = new HTTPPost();
$response = $binding->unpack($request->all());
$assertion = Assertion::fromXML($response->getDOMDocument());
if (!$assertion->validate()) {
abort(403, 'Invalid SAML assertion');
}
session(['saml_assertion' => $assertion->toXML()]);
}
return $next($request);
}
}
Register in app/Http/Kernel.php:
protected $middleware = [
\App\Http\Middleware\ValidateSamlSession::class,
];
Use Laravel queues for large SAML responses:
// app/Jobs/ProcessSamlAssertion.php
use SimpleSAML\SAML2\XML\saml\Assertion;
class ProcessSamlAssertion implements ShouldQueue
{
public function handle(Assertion $assertion)
{
$user = $this->mapSamlUserToLaravel($assertion);
auth()->login($user);
}
}
Dispatch
How can I help you explore Laravel packages today?