Single sign-on with Simplesamlphp and Drupal 9
Tags: drupal9, sso, singlesignon, simplesamlphp
Published: 2020-07-20 07:04:01

[![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/59hg6f818ownf7n0wamz.png)](https://www.buymeacoffee.com/Esnare)
Authentication is one of the most common features in any web development project. Therefore, Drupal, as one of the most popular CMS out there, has their own implementation and opinions about how authentication should be implemented.

However, what happens when there are multiple sites under the same domain/subdomain and there is the need for one single login for all them, Do you have to tell your users to create multiple user accounts on each app/website? 

What if the app stack is made with different frameworks or there are other projects linked to your app/website, Does that mean the dev team will have to deal with their own opinionated authentication process as well?

If you are asking yourself those questions, the answer may be that you need a single sign-on service for your applications.

There are many services out which will give you all the tools you need to have one single login across all your application stack. Nevertheless, this blog post will focus on describing how to have one single login between two Drupal sites using the SAML open standard.

## Here is our stack
- [Service Provider (Drupal 9 site)](https://github.com/esnaremaussa/drupal9-sp)
- [Identity Provider (Drupal 9 site)](https://github.com/esnaremaussa/drupal9-idp)
- [Lando](https://docs.lando.dev/config/drupal9.html)
- [Simplesamlphp](https://simplesamlphp.org/)

## Drupal 9 websites
We will need two Drupal 9 websites, the Identity Provider or IdP and the Service Provider or SP. 

These Drupal sites will be built with the composer *Recommended Project* from Drupal:

```bash
composer create-project drupal/recommended-project my_site_name_dir
```
> More about Drupal and Composer here: https://www.drupal.org/docs/develop/using-composer/using-composer-to-install-drupal-and-manage-dependencies

## Lando
This is a great project that uses Docker for creating containers with all the necessary services to run any Drupal application. 

The great thing about Lando and/or Docker is that help us with a dev stack for stateless applications. In other words, we will be able to spin off new versions of the site very easily without having to configure or making complex changes in our OS.

> [Lando documentation for Drupal](https://docs.lando.dev/config/drupal9.html)

## Simplesamlphp
This is the star of the show!
Simplesamlphp is a PHP library that implements the SAML (**S**ecurity **A**ssertion **M**arkup **L**anguage) open standard. 

We will be looking into more details further down the road, but for now, implementing this library will give us that single login for all the applications in our stack.

> [Checkout the documentation here](https://simplesamlphp.org)

## Last but not least
We will be also using these two fantastic projects:
- [Drupalauth](https://github.com/drupalauth/simplesamlphp-module-drupalauth): This is a module for the Simplesaml library which will connect Simplesaml with the Drupal user table.  
- [DrupalAuth for SimpleSAMLphp](https://www.drupal.org/project/drupalauth4ssp): This is a Drupal module which will handle the connection between Drupal and Simplesaml.

## What does IDP and SP even mean?
These two concepts are the key components in a SMAL authentication process.

### Identity provider
A SAML identity provider is a system entity that issues authentication assertions in conjunction with a single sign-on (SSO) profile of the Security Assertion Markup Language (SAML).

In the SAML domain model, a SAML authority is any system entity that issues SAML assertions.[OS 1] Two important examples of SAML authorities are the authentication authority and the attribute authority.

> Extracted from:  [wikipedia.org](https://en.wikipedia.org/wiki/Identity_provider_(SAML))

### Service provider
A SAML service provider is a system entity that receives and accepts authentication assertions in conjunction with a single sign-on (SSO) profile of the Security Assertion Markup Language (SAML).

In the SAML domain model, a SAML relying party is any system entity that receives and accepts information from another system entity.[OS 1] Of particular interest is a SAML relying party that receives and accepts a SAML assertion issued by a SAML authority.

> Extracted from: [wikipedia.org](https://en.wikipedia.org/wiki/Service_provider_(SAML))

### What does that mean for our project?
In our stack, the IdP will be our main Drupal site where all the usernames and passwords will be stored. In other words, think of this site as the master site where all the other apps will be connected to in other to login.

On the other hand, we have the SP, another Drupal 9 site, this site won't store any user data and, when users click on login, their login credentials (username and password) will be verified against the IdP.

## Enough theory, let's do this

I have created two GitHub repositories one for the IDP and another for the SP:

**The IDP**
https://github.com/esnaremaussa/drupal9-idp
**The SP**
https://github.com/esnaremaussa/drupal9-sp

Those repositories already include a **.lando.yml** file. Therefore, running **lando start** will spin off the Docker containers with the LAMP stack and any other fancy configuration for Drupal.

You will end up with two sites:
- https://idp.lndo.site/
- https://sp.lndo.site/

> NOTE: We will use Lando throughout this blog post, so I recommend to install it if you want to follow along.

## Configuring the IdP website
It's the moment for the [drupalauth](https://github.com/drupalauth/simplesamlphp-module-drupalauth) and [DrupalAuth for SimpleSAMLphp](https://www.drupal.org/project/drupalauth4ssp) projects to shine.

As per the Drupalauth project documentation, there are two ways to do this:

- [Authenticate against Drupal but use the SimpleSAMLphp login page](https://github.com/drupalauth/simplesamlphp-module-drupalauth#authenticate-against-drupal-but-use-the-simplesamlphp-login-page)
- [Authenticate against Drupal but use the Drupal login page](https://github.com/drupalauth/simplesamlphp-module-drupalauth#authenticate-against-drupal-but-use-the-drupal-login-page
)

We will be working with the 2nd option.

### Installing the Simplesamlphp library
We will run the following command on the terminal:

```bash
# Downloding the drupalauth4ssp module
$ lando composer require drupal/drupalauth4ssp
# Installing the drupalauth4ssp module
$ lando drush en drupalauth4ssp -y
```

With the above command, Composer will download all the third-party libraries we need for this project. Let me explain, the [Drupalauth4ssp](https://www.drupal.org/project/drupalauth4ssp) module has listed the [Simplesamlphp](https://simplesamlphp.org/download) library as well as [Drupalauth](https://github.com/drupalauth/simplesamlphp-module-drupalauth) module as a dependency. Therefore, when Composer is downloading the Drupalauth4ssp, it will also download all the libraries we require to get Simplesaml working on our project. 

On top of that, Composer will take care of versioning. In other words, it will make sure that the right versions are downloaded for this project.

You got to love composer!

In order to double-check that everything was installed correctly, go to your vendor folder and look for the simplesamlphp project, then check inside the modules folder, the Drupalauth module should be there too.

```bash
# Check if the simplesamlphp library is installed
ls -lha vendor/simplesamlphp/simplesamlphp
# Check if the Drupalauth module is installed
ls -lha vendor/simplesamlphp/simplesamlphp/modules/drupalauth
```

### Configuring the simplesamlphp library
At the moment, the Simplesamlphp library is downloaded but we can't access it from the browser. The reason being, Drupal only allows specific folders to be accessible by Apache/Nginx. Let's fix this by creating a symlink from the www folder, inside the simplesamlphp folder, to the web folder.

```bash
# Connect inside the appserver container
lando ssh
# Create the symlink
ln -nfs /app/vendor/simplesamlphp/simplesamlphp/www web/simplesaml
```

### .htaccess configuration
All directories on Drupal are protected by a configuration on the .htaccess file. Therefore, we need to whitelist Simplesmal directories so we can see it on the browser.

Just after this line:
```apache
RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$
```
Add the code:
```apache
  # Allow access to simplesamlphp scripts.
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/admin/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/sanitycheck/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/drupalauth/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/core/idp/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/saml/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/saml2/idp/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/saml2/idp/[^/]*\.php/drupal-userpass$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/saml/sp/[^/]*\.php/default-sp$
```

Although You should be able to access the Simplesaml from the browser now, you may be facing an error. This issues will be solved in the next step.

> IMPORTANT: From simplicity, I will be modifying configuration files from the vendor folder. However, this is not recommended and I will encourage you to look for the best way to persist the data for your configuration and deployment process.

### Config Files and Metadata
Currently, the IdP website is displaying some errors when accessing the simplesaml library at https://idp.lndo.site/simplesaml/

Let's fix this by copy the configuration files from the config-templates to the config folder:
```bash
cp -r vendor/simplesamlphp/simplesamlphp/config-templates/* vendor/simplesamlphp/simplesamlphp/config
```

And let's the same for the metadata files
```bash
cp -r vendor/simplesamlphp/simplesamlphp/metadata-templates/* vendor/simplesamlphp/simplesamlphp/metadata
```
### Now, let's dive deep into the Simplesamlphp configuration.

look for the config.php file inside the config folder and do the following changes:

```php
 'secretsalt' => 'RANDOM-NUMBER',
```

Running this command on the terminal will give you a random number for the secret salt:
```bash
LC_CTYPE=C tr -c -d '0123456789abcdefghijklmnopqrstuvwxyz' </dev/urandom | dd bs=32 count=1 2>/dev/null;echo
```
Master password for the Simplesamlphp backend:
```php
 'auth.adminpassword' => 'MySuperStrongPassword',
```

You can use the story type you'd like, for this example, we will use MySQL:
```php
 'store.type' => 'sql' 
 'store.sql.dsn' => 'mysql:host=database;dbname=drupal9',
 'store.sql.username' => 'drupal9',
 'store.sql.password' => 'drupal9',
```

Enable the IdP mode:
```php
 'enable.saml20-idp' => true,
```

Finally, add this line add the end of the config.php file:
```php
 $config['baseurlpath'] = 'https://'. $_SERVER['HTTP_HOST'] . '/simplesaml/';
```

### Certificates
Simplesaml requires certificates to be generate on the server for more security. A new cert folder will have to be created and generate the certificates there:
```bash
# Connect inside the appserver container
$ lando ssh
# Create cert folder
$ mkdir -p vendor/simplesamlphp/simplesamlphp/cert
# Go to the cert folder
$ cd vendor/simplesamlphp/simplesamlphp/cert
# Generate certificates
$ openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem
```

### simplesamlphp-module-drupalauth configuration
The Drupalauth project is a Simplesamlphp module which connects Simplesamlphp with the IdP website. In addition, it maps the Drupal user credentials in a way that Simplesamlphp can understand.

> NOTE: As stated at the beginning, we will be configuring the IdP site so it **Authenticates against Drupal but use the Drupal login page**. 

In the configuration folder, look for the **authsources.php file, then copy and paste the following code:
```php
 'drupal-userpass' => [
        'drupalauth:External',
        // The filesystem path of the Drupal directory.
        'drupalroot' => '/app/web/',
        // Whether to turn on debug
        'debug' => true,
        // the URL of the Drupal logout page
        'drupal_logout_url' => 'https://idp.lndo.site/user/logout',
        // the URL of the Drupal login page
        'drupal_login_url' => 'https://idp.lndo.site/user/login',
        // Which attributes should be retrieved from the Drupal site.
        'attributes' => array(
            array('field_name' => 'uid', 'attribute_name' => 'uid'),
            array('field_name' => 'roles', 'attribute_name' => 'roles', 'field_property' => 'target_id'),
            array('field_name' => 'name', 'attribute_name' => 'cn'),
            array('field_name' => 'mail', 'attribute_name' => 'mail'),
            array('field_name' => 'field_first_name', 'attribute_name' => 'givenName'),
            array('field_name' => 'field_last_name', 'attribute_name' => 'sn'),
            array('field_name' => 'field_organization', 'attribute_name' => 'ou', 'field_property' => 'target_id'),
        ),
    ],
```

### Metadata configuration
For this section, we will be modifying the files inside the metadata folder:

```bash
ls vendor/simplesamlphp/simplesamlphp/metadata
```
At the moment, we just need to locate the saml20-idp-hosted.php and the drupal-userpass value on the auth key.

```php
'auth' => 'drupal-userpass',
```

There will more metadata we need to configure but let's create the SP website first.

> **Testing your work so far**
There is a very simple way to know if you have done the steps above properly. First, go to the Simplesamlphp backend, https://idp.lndo.site/simplesaml. Then, go to the *Test authentication sources* section. Finally, click on the **drupal-userpass** option listed there.

> After you click it, you should be taken to the Drupal login page. That means you're on track! ;)


### Configuring the Service Provider (SP) 
Let's move on now and create a consumer website. We'll be using the https://github.com/esnaremaussa/drupal9-sp repository to build this project.

Run **lando start** and you should be running your site at https://sp.lndo.site

Next, download and install [simpleSAMLphp Authentication](https://www.drupal.org/project/simplesamlphp_auth) module from Drupal.

 ```bash
# Download the simplesamlphp_auth Authentication module
$ lando composer require drupal/simplesamlphp_auth 
# Install the simplesamlphp_auth Authentication module
$ lando drush en simplesamlphp_auth -y
```

The simplesamlphp_auth has the Simplesamlphp library as a dependency. Therefore, you should be able to find it in your vendor folder after running the composer install command.

### Configuring the simplesamlphp_auth module
This module is very flexible and offers a good set of configuration for your Drupal site. However, the module is disabled by default. 

For this example, we will make minimal changes to this module. Thus, go to the *User info and syncing* tab and add the value **uid** for the following fields:
- SimpleSAMLphp attribute to be used as unique identifier for the user.
- SimpleSAMLphp attribute to be used as username for the user.

![SimpleSamlConfiguration Drupal Backend](https://dev-to-uploads.s3.amazonaws.com/i/z86vb7t1fqcu3327tzud.png)

### Configuring the Simplesamlphp library for the  SP
The Simplesamlphp library is a dependency for the simplesamlphp_auth module. Therefore, it should be downloaded in the vendor folder.

 ```bash
# Check if the simplesamlphp library is installed
$ ls -lha vendor/simplesamlphp/simplesamlphp
```

The configuration steps for the SP are pretty much the same as the IdP. Thus here is a quick summary of the commands for configuring the Simplesamlphp library:


```bash
# SSH the appserver container
lando ssh
# Create the symlink
ln -nfs /app/vendor/simplesamlphp/simplesamlphp/www web/simplesaml
```

**.httaccess configuration**
Just after this line:
```apache
RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$
```
Add the code:
```apache
  # Allow access to simplesamlphp scripts.
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/admin/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/sanitycheck/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/drupalauth/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/core/idp/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/saml/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/saml2/idp/[^/]*\.php$
  RewriteCond %{REQUEST_URI} !/simplesaml/saml2/idp/[^/]*\.php/drupal-userpass$
  RewriteCond %{REQUEST_URI} !/simplesaml/[^/]*\.php/saml/sp/[^/]*\.php/default-sp$
```

**Config Files and Metadata**

Add the code:
```bash
# Copy configuration template files
cp -r vendor/simplesamlphp/simplesamlphp/config-templates/* vendor/simplesamlphp/simplesamlphp/config
# Copy metadata template files
cp -r vendor/simplesamlphp/simplesamlphp/metadata-templates/* vendor/simplesamlphp/simplesamlphp/metadata
```

**Making changes on the config.php file**
```php
'secretsalt' => 'RANDOM-NUMBER',

'auth.adminpassword' => 'MySuperStrongPassword',

'store.type' => 'sql' 
'store.sql.dsn' => 'mysql:host=database;dbname=drupal9',
'store.sql.username' => 'drupal9',
'store.sql.password' => 'drupal9',

#Finally, add this line at the end of the config.php file:
$config['baseurlpath'] = 'https://'. $_SERVER['HTTP_HOST'] . '/simplesaml/';
```

**Making changes on the authsources.php file**
For the SP, we will use the *default-sp* authentication source. 

Checkout the code snipped:
```php
'idp' => 'https://idp.lndo.site/simplesaml/saml2/idp/metadata.php',
```

**Certificates**

```bash
# SSH the appserver container
$ lando ssh
# Create cert folder
$ mkdir -p vendor/simplesamlphp/simplesamlphp/cert
# Go to the cert folder
$ cd vendor/simplesamlphp/simplesamlphp/cert
# Generate certificates
$ openssl req -newkey rsa:3072 -new -x509 -days 3652 -nodes -out server.crt -keyout server.pem
```

**Metadata SP configuration**
Open the file saml20-idp-hosted.php, inside the metadata folder and add the following code:
```php
'auth' => 'drupal-userpass',
```

### Exchanging Metadata between the SP and Idp
This is the final step!

**Let's add the SP metadata on the IdP**
On your SP Simplesaml website, go to the *Federation* tab, look for the *SAML 2.0 SP Metadata* section and click on *show metadata*.

You should copy the PHP array on the metadata section.

> If you have been following along, this should be SP metadata URL: https://sp.lndo.site/simplesaml/module.php/saml/sp/metadata.php/default-sp?output=xhtml

Then, go to your IdP codebase, look for the metadata folder and locate the *saml20-sp-remote.php* file, at the end of that file you should paste the PHP array.

```php
# This is an example of the first lines of the metadata array.
$metadata['https://sp.lndo.site/simplesaml/module.php/saml/sp/metadata.php/default-sp'] = array (
  'SingleLogoutService' => 
  array (
    0 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
      'Location' => 'https://sp.lndo.site/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp',
    ),
.
.
.
```

**Let's add the IdP metadata on the SP**
On your IdP Simplesaml website, go to the *Federation* tab, look for the *SAML 2.0 IdP Metadata* section and click on *show metadata*.

> If you have been following along, this should be IdP metadata URL: https://idp.lndo.site/simplesaml/saml2/idp/metadata.php?output=xhtml

Then, go to your SP codebase, look for the metadata folder and locate the *saml20-idp-remote.php* file, at the end of that file you should paste the PHP array.

```php
# This is an example of the first lines of the metadata array.
$metadata['https://idp.lndo.site/simplesaml/saml2/idp/metadata.php'] = array (
  'metadata-set' => 'saml20-idp-remote',
  'entityid' => 'https://idp.lndo.site/simplesaml/saml2/idp/metadata.php',
  'SingleSignOnService' => 
.
.
.
```
### Known issues
Currently, there is just one issue with the Drupalauth Simplesamlphp module. The Crypt::hashEquals function is deprecated for Drupal 9 and the hash_equals from PHP should be used instead.

Have a look here: https://github.com/drupalauth/simplesamlphp-module-drupalauth/issues/62



### Wrapping Up 
And there you are!
You should have now the Simplesamlphp library up and running on both SP and IdP sites. Also, you should be able to authenticate on the SP with the users registered on the IdP.

**Statesless version**
Lando not only provides the option of spin-off environments for Drupal development. It also allows to run configuration code before/after the services are up and running. This is possible because Lando is a superset of Docker and you should be able to do the same things that you normally do with Docker Compose.

In other words, all those commands needed for the Simplesamlphp library configuration can be added to the IdP and SP websites before the app services are running, just like a CI/CD system.

Go to any of these two projects:

- [Service Provider (Drupal 9 site)](https://github.com/esnaremaussa/drupal9-sp)
- [Identity Provider (Drupal 9 site)](https://github.com/esnaremaussa/drupal9-idp)

There should be a branch called **stateless**. In that branch, all the configuration explained here will be done automatically just by running **lando start**.