Laravel simpleSAML with ADFS setup guide - Part 1

I was tasked with creating a new site which had to interact with a federated service, namely ADFS and chose to use Laravel as a base. Little did I know it was terribly documented and not for beginners.


So heres the process:

Laravel setup

  • Setup a laravel project, do this however you normally do. I use the following, replacing name of project with the project name:

composer create-project --prefer-dist laravel/laravel {nameofproject}
Note: This creates a project in a directory  by the name of the project.

  • Setup your external apache bits and pieces, virtual servers, certificates for https etc.

  • For testing purposes at this stage, artisan an auth out in the project

php artisan make:auth

composer require aacotroneo/laravel-saml2
Next we need to gather information and setup adfs

ADFS

So the first thing we need to do here is create a new relying party. We can't do this until we have metadata. We can't do this properly until we have certificates and keys.

Start with certificates. We need to find 3 certificates, however in practice we wont need them all.

Certificates that ADFS uses


The certificates I needed were the * certificate for all subdomains on a domain. This is the one you will be identifying your domain with. A signing certificate and the ADFS certificate. I made sure I got all of them just in case. I turned them all into cer files out of the ADFS management system as Base 64, opened with notepad and copied the certificates code into named comments in the saml2_settings.php file. Once I had them I could use them to find and solve issues I was having.

Here are some ADFS screenshots for exporting the certificate in Base 64:
First open the certificate, go to the details tab and choose copy to file...

Don't export the private key, chances are you already have the one you need somewhere in your laravel setup

Choose Base 64

Why Base 64?

Its a pretty simple reason, the code used to link the certificates from Laravel needs to be in this format OR as cer and key files depending on what your linking. Heres a sample file:

-----BEGIN CERTIFICATE-----
MIICYzCCAcygAwIBAgIBADANBgkqhkiG9w0BAQUFADAuMQswCQYDVQQGEwJVUzEM 
MAoGA1UEChMDSUJNMREwDwYDVQQLEwhMb2NhbCBDQTAeFw05OTEyMjIwNTAwMDBa 
Fw0wMDEyMjMwNDU5NTlaMC4xCzAJBgNVBAYTAlVTMQwwCgYDVQQKEwNJQk0xETAP 
BgNVBAsTCExvY2FsIENBMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQD2bZEo 
7xGaX2/0GHkrNFZvlxBou9v1Jmt/PDiTMPve8r9FeJAQ0QdvFST/0JPQYD20rH0b 
imdDLgNdNynmyRoS2S/IInfpmf69iyc2G0TPyRvmHIiOZbdCd+YBHQi1adkj17ND 
cWj6S14tVurFX73zx0sNoMS79q3tuXKrDsxeuwIDAQABo4GQMIGNMEsGCVUdDwGG 
+EIBDQQ+EzxHZW5lcmF0ZWQgYnkgdGhlIFNlY3VyZVdheSBTZWN1cml0eSBTZXJ2 
ZXIgZm9yIE9TLzM5MCAoUkFDRikwDgYDVR0PAQH/BAQDAgAGMA8GA1UdEwEB/wQF 
MAMBAf8wHQYDVR0OBBYEFJ3+ocRyCTJw067dLSwr/nalx6YMMA0GCSqGSIb3DQEB 
BQUAA4GBAMaQzt+zaj1GU77yzlr8iiMBXgdQrwsZZWJo5exnAucJAEYQZmOfyLiM 
D6oYq+ZnfvM0n8G/Y79q8nhwvuxpYOnRSAXFp6xSkrIOeZtJMY1h00LKp/JX3Ng1 
svZ2agE126JHsQ0bhzN5TKsYfbwfTwfjdWAGy6Vf1nYi/rO+ryMO
-----END CERTIFICATE----- 

Note: You only need the bit between BEGIN and END CERTIFICATE.

Relying Party Trusts

Next, we need to setup a relying party trust. This is pretty easy as long as you have an internet facing server with working certificates but I think you can probably do it in a test environment with self signed certificates too.

So the URL you need to setup the relying party trust from laravel (once you have finished filling in your saml2_settings.php file) is https://laravelserverlocation/saml2/metadata

Sample Metadata

Right so I guess we need to do the config!

SAML Config

If you don't have the settings file in your laravel config directory already run the following command.
php artisan vendor:publish
This will create the file and if you have included any other features into laravel, this may create extra files for them too.

Now we have the settings file lets take a look:

$idp_host = env('SAML2_IDP_HOST', 'http://localhost:8000/simplesaml');
    'useRoutes' => true,
    'routesPrefix' => '/saml2',
    'routesMiddleware' => [],
    'retrieveParametersFromServer' => false,
    'logoutRoute' => '/',
    'loginRoute' => '/',
    'errorRoute' => '/',
    'strict' => true,
    'debug' => env('APP_DEBUG', false),
    'proxyVars' => false,
    'sp' => array(
        'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
        'x509cert' => env('SAML2_SP_x509',''),
        'privateKey' => env('SAML2_SP_PRIVATEKEY',''),
        'entityId' => env('SAML2_SP_ENTITYID',''),
        'assertionConsumerService' => array(
            'url' => '',
        ),
        'singleLogoutService' => array(
            'url' => '',
        ),
    ),
    'idp' => array(
        'entityId' => env('SAML2_IDP_ENTITYID', $idp_host . '/saml2/idp/metadata.php'),
        'singleSignOnService' => array(
            'url' => $idp_host . '/saml2/idp/SSOService.php',
        ),
        'singleLogoutService' => array(
            'url' => $idp_host . '/saml2/idp/SingleLogoutService.php',
        ),
        'x509cert' => env('SAML2_IDP_x509', 'MIID/TCCAuWgAwIBAgIJAI4R3WyjjmB1MA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJBUjEVMBMGA1UECAwMQnVlbm9zIEFpcmVzMRUwEwYDVQQHDAxCdWVub3MgQWlyZXMxDDAKBgNVBAoMA1NJVTERMA8GA1UECwwIU2lzdGVtYXMxFDASBgNVBAMMC09yZy5TaXUuQ29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbmlAc2l1LmVkdS5hcjAeFw0xNDEyMDExNDM2MjVaFw0yNDExMzAxNDM2MjVaMIGUMQswCQYDVQQGEwJBUjEVMBMGA1UECAwMQnVlbm9zIEFpcmVzMRUwEwYDVQQHDAxCdWVub3MgQWlyZXMxDDAKBgNVBAoMA1NJVTERMA8GA1UECwwIU2lzdGVtYXMxFDASBgNVBAMMC09yZy5TaXUuQ29tMSAwHgYJKoZIhvcNAQkBFhFhZG1pbmlAc2l1LmVkdS5hcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbzW/EpEv+qqZzfT1Buwjg9nnNNVrxkCfuR9fQiQw2tSouS5X37W5h7RmchRt54wsm046PDKtbSz1NpZT2GkmHN37yALW2lY7MyVUC7itv9vDAUsFr0EfKIdCKgxCKjrzkZ5ImbNvjxf7eA77PPGJnQ/UwXY7W+cvLkirp0K5uWpDk+nac5W0JXOCFR1BpPUJRbz2jFIEHyChRt7nsJZH6ejzNqK9lABEC76htNy1Ll/D3tUoPaqo8VlKW3N3MZE0DB9O7g65DmZIIlFqkaMH3ALd8adodJtOvqfDU/A6SxuwMfwDYPjoucykGDu1etRZ7dF2gd+W+1Pn7yizPT1q8CAwEAAaNQME4wHQYDVR0OBBYEFPsn8tUHN8XXf23ig5Qro3beP8BuMB8GA1UdIwQYMBaAFPsn8tUHN8XXf23ig5Qro3beP8BuMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGu60odWFiK+DkQekozGnlpNBQz5lQ/bwmOWdktnQj6HYXu43e7sh9oZWArLYHEOyMUekKQAxOK51vbTHzzw66BZU91/nqvaOBfkJyZKGfluHbD0/hfOl/D5kONqI9kyTu4wkLQcYGyuIi75CJs15uA03FSuULQdY/Liv+czS/XYDyvtSLnu43VuAQWN321PQNhuGueIaLJANb2C5qq5ilTBUw6PxY9Z+vtMjAjTJGKEkE/tQs7CvzLPKXX3KTD9lIILmX5yUC3dLgjVKi1KGDqNApYGOMtjr5eoxPQrqDBmyx3flcy0dQTdLXud3UjWVW3N0PYgJtw5yBsS74QTGD4='),
    ),

    'security' => array(
        'nameIdEncrypted' => false,
        'authnRequestsSigned' => false,
        'logoutRequestSigned' => false,
        'logoutResponseSigned' => false,
        'signMetadata' => false,
        'wantMessagesSigned' => false,
        'wantAssertionsSigned' => false,
        'wantNameIdEncrypted' => false,
        'requestedAuthnContext' => true,
    ),
    'contactPerson' => array(
        'technical' => array(
            'givenName' => 'name',
            'emailAddress' => 'no@reply.com'
        ),
        'support' => array(
            'givenName' => 'Support',
            'emailAddress' => 'no@reply.com'
        ),
    ),
    'organization' => array(
        'en-US' => array(
            'name' => 'Name',
            'displayname' => 'Display Name',
            'url' => 'http://url'
        ),
    ),
);
I have removed all comments to make it easier to see how many options there are here. Lets start at the top. We are using ADFS so lets set the $idp_host to the correct ADFS server URL:
$idp_host = env('SAML2_IDP_HOST', 'https://ADFSSERVERADDRESS/adfs/ls/');
The next bit is the SP and IDP variable arrays:

'sp' => array()
'idp' => array()
SP is pretty easy, just leave it all blank BUT you must make sure you place certificates in directories in the laravel project then! Find This folder in your laravel project:
vendor\onelogin\php-saml\certs
IMPORTANT: The things we need to place here are a crt file and a key file. I had the domain * certificate and key which you must name sp.crt and sp.key but I think this will work for testing with self signed. This is the certificate you will be using for the site.

We're over halfway now!

Right, now for the important settings in the file, the idp array.

Lets have another look at the blank one, which contains a certificate pre filled. This is not going to stay.
'idp' => array(
        'entityId' => env('SAML2_IDP_ENTITYID', $idp_host . '/saml2/idp/metadata.php'),
        'singleSignOnService' => array(
            'url' => $idp_host . '/saml2/idp/SSOService.php',
        ),
        'singleLogoutService' => array(
            'url' => $idp_host . '/saml2/idp/SingleLogoutService.php',
        ),
        'x509cert' => env('SAML2_IDP_x509', 'CERT TRUNCATED'),
    ),
I didn't need to worry about implementing a logout service in my project so I will skip over that entirely here.

As we are using ADFS, this bit becomes easier:
'entityId' => env('SAML2_IDP_ENTITYID', 'http://ADFSSERVERADDRESS/adfs/services/trust'),
Ok next we need to sort out the singlesignonservice and singlelogoutservice arrays. Change both arrays to contain the following url parameter:
'url' => $idp_host 
Finally we need the x509cert filled in.

Now because we obtained all 3 certificates data earlier, we can use the signing certificate here without any further worries, literally paste in the signing certificates base 64 code.

So, we are now almost finished, lets look at the final few lines in the security parameters. By default there is only one set to true:
'requestedAuthnContext' => true,
Swap it out to false and we can then test with relative ease without any security garbage getting in the way.

The final part of the settings file is about setting up contact person and organisation, you can fill this in if you want to.

Pretty easy so far. If this URL (https://laravelserverlocation/saml2/metadata) is successful it should pull settings into ADFS automatically once we have setup the relying party.


Claims aware

Add the laravel metadata url

Allow all for now
Check through all tabs and complete the process

If all went well you should now have a relying party setup and almost be finished.

Whats left?


  • Setup ADFS Claims
  • Create a login listener
  • Setup redirects on authentication


ADFS Claims

This is where I needed some assistance and where a beginner linking these systems will fall. You HAVE to setup claims to be able to pass data through and back out from ADFS to your laravel project.

Claims you should setup:

  1. Data you want to retrieve
  2. Data the server needs to be able to pass them back
Basically You have to setup at least one of these and I would recommend 2 (getting relevant data back is handy).

Transform the incoming claim

Convert Name to Name ID and ensure the persistent identifier is selected, pass through ALL claims
Next send through an LDAP attribute claim. This is for data from Active Directory
Type a name for the claim, items on the left are converted to variables on the right here. Dont forget to select Active Directory as the attribute store (forgot to in the screenshot)
If you have all this setup now, the only thing left is to handle the login.

Handling the login

We need to create some code now! Yes, actual code!

I will link a separate blog post here for the next steps. I am finishing an implementation of the ADFS login feature and will use the code from that for demonstration.

I did start part 2 but due to a change in how I am most likely going to go forward with this, I have suspended the post for now.

Resources

As you can see I have used a LOT of resources to help with this setup, some are linked, some are missing (because I closed them and there are so many I couldn't remember all the useful ones).

Resources used:

Comments

Gabor said…
What about Part 2?
Adam W said…
I gave up when I found something didnt work properly. It wasnt what I was trying to do and ended up working on another method. I will finish up part two at some point. Sorry about that.
Olga said…
Would be great to read the second part of this nice tutorial
Adam W said…
Funny enough I'm just looking for something relating to this so I might end up doing the rest anyway!