Play it Again, SAML

I had to integrate SAML authetication on a Laravel application, so I composer required the laravel-saml2 package (actually, the remove_mcrypt branch. You know: mcrypt has been dropped in PHP 7.2...) and configured it to attach to the SimpleSAML-PHP instance I recently deployed.

Of course nothing worked out of the box, so my experience is worth a blog post.


On the server side (SimpleSAML-PHP): the standard response for authentication includes the password attribute. With the user's password hash in binary format. And, aside the security implications of this, the binary hash is not valid UTF-8 encoded and the OneLogin\Saml2::loadXML() function fails miserably.

Solution: given that I've not (yet) found a way to suppress the password attribute alltogether, we can transfer it as BASE64.

$metadata['https://example.com/saml2/metadata'] = array(
    'AssertionConsumerService' => 'https://example.com/saml2/acs',
    'SingleLogoutService' => 'https://example.com/saml2/sls',
    'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
    'simplesaml.nameidattribute' => 'uid',

    // The secret sauce...
    'attributeencodings' => [
        'password' => 'base64',
    ],
);

On the client side (Laravel): as suggested in laravel-saml2 README file I provided an event listener in App\Providers\EventServiceProvider::boot(), to trigger Laravel's authentication on SAML successful login, but I obtained a mere redirected to the homepage with no authentication.

The trick: laravel-saml2 routes have to be routed through the web middleware, otherwise no session (so: no authentication) is started. Luckily it exists the routesMiddleware setting in config/saml2_settings.php, to be set as

'routesMiddleware' => ['web'],

But here comes another pitfall: web middleware implies VerifyCsrfToken, which implies a valid CSRF token to be added to POST'd requests, but the POST sent by the remote service provider on the saml2/acs endpoint of course do not have any CSRF token and the notification for the correct login fails. We can easily leverage the $except array in App\Http\Middleware\VerifyCsrfToken file, as following:

class VerifyCsrfToken extends Middleware
{
    protected $except = [
        '/saml2/acs'
    ];
}

I hate SAML.