onelogin/php-saml
PHP toolkit for adding SAML 2.0 SSO to your app. Handles login/logout, assertion processing, metadata generation, and signature/encryption validation, with strict security options and PHP 7.3+ (4.x) or older PHP support via branches.
Install the package:
composer require onelogin/php-saml
Use branch 4.X for PHP 7.3+ or 3.X for PHP 7.0-7.2.
Configure SAML settings:
Replace settings_example.php (from the package) with your own config/saml.php:
return [
'strict' => true,
'debug' => env('SAML_DEBUG', false),
'security' => [
'nameIdEncrypted' => false,
'authnRequestsSigned' => true,
'wantAssertionsSigned' => true,
'wantAssertionsEncrypted' => false,
'signatureAlgorithm' => 'sha256',
'digestAlgorithm' => 'sha256',
],
'sp' => [
'entityId' => 'https://your-app.test/saml/metadata',
'assertionConsumerService' => [
'url' => route('saml.acs'),
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
],
'singleLogoutService' => [
'url' => route('saml.sls'),
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
'x509cert' => file_get_contents(base_path('storage/saml/sp.crt')),
'privateKey' => file_get_contents(base_path('storage/saml/sp.key')),
],
'idp' => [
'entityId' => 'https://idp.example.com/saml/metadata',
'singleSignOnService' => [
'url' => 'https://idp.example.com/saml/sso',
'binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
'x509cert' => file_get_contents(base_path('storage/saml/idp.crt')),
],
];
Create routes (routes/saml.php):
Route::get('/saml/metadata', [SamlController::class, 'metadata'])->name('saml.metadata');
Route::post('/saml/acs', [SamlController::class, 'acs'])->name('saml.acs');
Route::post('/saml/sls', [SamlController::class, 'sls'])->name('saml.sls');
Route::get('/saml/login', [SamlController::class, 'login'])->name('saml.login');
First use case: SP-Initiated SSO In your controller:
use OneLogin\Saml2\Auth;
public function login()
{
$settings = config('saml');
$auth = new Auth($settings);
return redirect()->to($auth->login());
}
SP-Initiated SSO:
Auth::login() → Redirects to IdP.acs.php (or your custom route) with SAML Response.acs():
$auth = new Auth($settings);
$errors = $auth->validateAuthnResponse();
if (empty($errors)) {
$attributes = $auth->getAttributes();
auth()->loginUsingId($attributes['email'][0]); // Custom logic
return redirect()->intended('/dashboard');
}
return back()->withErrors($errors);
IdP-Initiated SSO:
acs.php with RelayState.Single Logout (SLO):
Auth::logout() → Redirects to IdP.sls.php:
$auth = new Auth($settings);
$logoutRequest = $auth->getLogoutRequest();
return redirect()->to($logoutRequest['url']);
sls():
$auth = new Auth($settings);
$errors = $auth->validateLogoutResponse();
if (empty($errors)) {
auth()->logout(); // Custom logic
}
Session Handling:
Use Laravel’s session to store LastMessageID/LastAssertionID for replay attack prevention:
session(['last_saml_message_id' => $auth->getLastMessageId()]);
Check on validation:
if (session('last_saml_message_id') === $auth->getLastMessageId()) {
abort(403, 'Replay attack detected');
}
Metadata:
Publish SP metadata via metadata.php:
$settings = config('saml');
$metadata = new \OneLogin\Saml2\Metadata($settings);
return response()->make($metadata->getMetadata(), 200, ['Content-Type' => 'text/xml']);
Attribute Mapping: Map IdP attributes to Laravel users dynamically:
$email = $auth->getAttributes()['email'][0] ?? null;
$user = User::firstOrCreate(['email' => $email]);
Error Handling: Centralize SAML errors in a middleware:
public function handle($request, Closure $next)
{
try {
return $next($request);
} catch (\OneLogin_Saml2_Error $e) {
return back()->withErrors(['saml' => $e->getMessage()]);
}
}
Certificate Management:
vendor/onelogin/php-saml/certs/ are overwritten on composer update.storage/saml/ and reference them in config/saml.php:
'sp' => [
'x509cert' => file_get_contents(storage_path('saml/sp.crt')),
'privateKey' => file_get_contents(storage_path('saml/sp.key')),
],
Strict Mode:
strict: true in production enables insecure defaults (e.g., SHA1).config/saml.php and validate signatures:
'strict' => env('APP_ENV') === 'production',
'security' => [
'signatureAlgorithm' => 'sha256',
'digestAlgorithm' => 'sha256',
],
RelayState Validation:
RelayState values enable open redirect attacks (CWE-601).$allowedUrls = ['/dashboard', '/profile'];
if (!in_array($auth->getRelayState(), $allowedUrls)) {
abort(400, 'Invalid RelayState');
}
Session Fixation:
if ($auth->isAuthenticated()) {
session_regenerate_id(true);
}
IdP Metadata Parsing:
$idpMetadata = file_get_contents('https://idp.example.com/saml/metadata');
$settings['idp']['x509cert'] = $this->extractCertFromMetadata($idpMetadata);
Enable Debugging:
Set 'debug' => true in config/saml.php to log SAML messages to storage/logs/saml.log.
Validate XML:
Use OneLogin\Saml2\Utils::xmlErrorLog() to debug malformed SAML responses:
\OneLogin\Saml2\Utils::xmlErrorLog($samlResponse);
Test Locally: Use SimpleSAMLphp as a test IdP with self-signed certs.
Custom Assertion Processing:
Extend OneLogin\Saml2\Auth to add logic:
class CustomAuth extends \OneLogin\Saml2\Auth
{
public function getUser()
{
$attributes = $this->getAttributes();
return User::where('email', $attributes['email'][0])->first();
}
}
Hooks for Pre/Post-Validation: Use Laravel events to intercept SAML flows:
event(new SamlValidating($auth));
event(new SamlValidated($auth, $errors));
3
How can I help you explore Laravel packages today?