How to Configure SimpleSAMLphp for Drupal 8 on Acquia

SimpleSAML PHP Drupal AcquiaThese are instructions on how to configure SimpleSAMLphp library for Drupal 8 on Acquia, the configuration settings may vary depending on the ADFS configuration.

Requirements

  1. Download SimpleSAMLphp (I used version 1.14.2, latest version is always recommended).
  2. Drupal 8 site (latest version recommended).
  3. simpleSAMLphp Authentication Drupal 8 module.
  4. Drupal 8 site running on Acquia.

Install SimpleSAMLphp

  • Extract the SimpleSAML library to the root directory of your repository alongside docroot, acquia-utils and library.

  • In your docroot directory, create a symbolic link (name it simplesaml) that points to the to the simplesamlphp-1.14.2/www directory in theSimpleSAMLphp library.

$ cd docroot
$ ln -s ../simplesamlphp-1.14.2/www ./simplesaml
$ git add ../simplesamlphp-1.14.2
$ git add simplesaml
$ git commit -m "Add SimpleSAMLphp library. Add SimpleSAML symlink."
  • Generate certificates as needed and add them to your repository /simplesamlphp-1.14.2/cert

Important: Make sure you are using correct encryption. Microsoft announced that they wouldn't be accepting SHA1 certificates after 2016. Read more. Generate SHA256 certificates:

$ cd cert
$ openssl req -x509 -sha256 -nodes -days 3652 -newkey rsa:2048 -keyout saml.pem -out saml.crt

SimpleSAMLphp Configuration

Set up your /simplesamlphp-1.14.2/config/config.php file. Acquia provides a code snippet that helps properly setup all the variables required by the library. The code snippet shoud go AFTER the config = array() variable:

$config = (
  // Default Library settings template.
  // I would not recommend to change any of those settings
  // Instead just override them below the `$config` varaible.
);

// All custom changes below. Modify as needed.
// Defines account specific settings.
// $ah_options['database_name'] should be the Acquia Cloud workflow database name which
// will store SAML session information.set
// You can use any database that you have defined in your workflow.
// Use the database "role" without the stage ("dev", "stage", or "test", etc.)
$ah_options = array(
  'database_name' => '[DATABASE-NAME]',
  'session_store' => array(
    'prod' => 'memcache', // This can be either `memcache` or `database`
    'test' => 'memcache', // This can be either `memcache` or `database`
    'dev'  => 'database', // This can be either `memcache` or `database`
  ),
);
// Base URL
$config['baseurlpath'] = 'https://'. $_SERVER['HTTP_HOST'] .'/simplesaml/';
// Remove memcache prefix
unset($config['memcache_store.prefix']);
// Set some security and other configs that are set above, however we
// overwrite them here to keep all changes in one area
$config['technicalcontact_name'] = "Technical Contact Name";
$config['technicalcontact_email'] = "email@example.com";
// Change these for your installation
$config['secretsalt'] = '[YOUR-SECERET-SALT]';
$config['auth.adminpassword'] = '[ADMIN-PASSWORD]';
$config['admin.protectindexpage'] = TRUE;
// Prevent Varnish from interfering with SimpleSAMLphp.
setcookie('NO_CACHE', '1');
if (empty($_ENV['AH_SITE_ENVIRONMENT'])) {
  // add any local configuration here
} else {
  $ah_options['env'] = $_ENV['AH_SITE_ENVIRONMENT'];
  $config = acquia_logging_config($config);
  $config = acquia_session_store_config($config, $ah_options);
}
function acquia_session_store_config($config, $ah_options) {
  if ($ah_options['session_store'][$ah_options['env']] == 'memcache') {
    $config = mc_session_store($config);
  } elseif ($ah_options['session_store'][$ah_options['env']] == 'database') {
    $config = sql_session_store($config, $ah_options['database_name']);
  }
  return $config;
}
function acquia_logging_config($config) {
  // Set log location, as specified by Acquia
  $config['logging.handler'] = 'file';
  $config['loggingdir'] = dirname($_ENV['ACQUIA_HOSTING_DRUPAL_LOG']);
  $config['logging.logfile'] = 'simplesamlphp-' . date("Ymd") . '.log';
  return $config;
}
function mc_session_store($config) {
  $config['store.type'] = 'memcache';
  $config['memcache_store.servers'] = mc_info();
  return $config;
}
function mc_info() {
  $creds_json = file_get_contents('/var/www/site-php/' . $_ENV['AH_SITE_NAME'] . '/creds.json');
  $creds = json_decode($creds_json, TRUE);
  $mc_server = array();
  $mc_pool = array();
  foreach ($creds['memcached_servers'] as $fqdn) {
    $mc_server['hostname'] = preg_replace('/:.*?$/', '', $fqdn);
    array_push($mc_pool, $mc_server);
  }
  return array($mc_pool);
}
function sql_session_store($config, $database_name) {
  $creds = db_info($database_name);
  $config['store.type'] = 'sql';
  $config['store.sql.dsn'] = sprintf('mysql:host=%s;port=%s;dbname=%s', $creds['host'], $creds['port'], $creds['name']);
  $config['store.sql.username'] = $creds['user'];
  $config['store.sql.password'] = $creds['pass'];
  $config['store.sql.prefix'] = 'simplesaml';
  return $config;
}
function db_info($db_name) {
  $creds_json = file_get_contents('/var/www/site-php/' . $_ENV['AH_SITE_NAME'] . '/creds.json');
  $databases = json_decode($creds_json, TRUE);
  $db = $databases['databases'][$db_name];
  $db['host'] = ($host = ah_db_current_host($db['db_cluster_id'])) ? $host : key($db['db_url_ha']);
  return $db;
}
function ah_db_current_host($db_cluster_id) {
  require_once("/usr/share/php/Net/DNS2_wrapper.php");
  try {
    $resolver = new Net_DNS2_Resolver(array('nameservers' => array('127.0.0.1', 'dns-master')));
    $response = $resolver->query("cluster-{$db_cluster_id}.mysql", 'CNAME');
    $cached_id = $response->answer[0]->cname;
  }
  catch (Net_DNS2_Exception $e) {
    $cached_id = "";
  }
  return $cached_id;
}
  • The latest version of the Acquia SAML config snippet can be downloaded from here: https://gist.github.com/acquialibrary/8059715
  • Notice: $config['baseurlpath'] = 'https://'. $_SERVER['HTTP_HOST'] .'/simplesaml/';. This forces SAML library to server over HTTPS.
  • Now you should be able to login to your SimpleSAMLphp interface.

    • Visit https://example.prod.acquia-sites.com/simplesaml
    • Enter the password from the config.php value of the auth.adminpassword parameter.
    • Make sure Checking your PHP installation on the config page has no red flags. https://example.prod.acquia-sites.com/simplesaml/module.php/core/frontpage_config.php
  • Next, configure /simplesamlphp-1.14.2/config/authsources.php file

$env = !empty($_ENV['AH_SITE_ENVIRONMENT']) ? '-' . $_ENV['AH_SITE_ENVIRONMENT'] : '';

  $config = array(
  'admin' => array(
    'core:AdminPassword',
  ),
  'default-sp' => array(
   'saml:SP',

   // You can get this from ADFS Federation file
   // Contact your ADFS administrator
   // to obtain this information.
   'entityID'             => 'urn:drupal:adfs-saml' . $env,
   'idp'                  => 'http://example.org/adfs/services/trust',
   'NameIDPolicy'         => null,
   'redirect.sign'        => true,
   'assertion.encryption' => true,
   'sign.logout'          => true,

   // Generate using openssl, @see example above.
   // These are the certs from `/cert` directory.
   'privatekey'           => 'saml.pem',
   'certificate'          => 'saml.crt',
   // Defaults to SHA1 (http://www.w3.org/2000/09/xmldsig#rsa-sha1)
   'signature.algorithm'  => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
  ),
);
  • Next you will need a Federation metadata file from the ADFS. Contact your ADFS administrator to generate the file.

  • After you obtain the federation metadata file use the XML to SimpleSAMLphp metadata converter to generate other config files. The converter is a part of the SimpleSAMLphp library and can be accessed through web: https://example.prod.acquia-sites.com/simplesaml/admin/metadata-converter.php

The result should look similar to the following:

saml20-sp-remote

Put the results of this section into /simplesamlphp-1.14.2/metadata/saml20-sp-remote.php

$metadata['http://example.org.org/adfs/services/trust'] = array (
  'entityid' => 'http://example.org/adfs/services/trust',
  'contacts' => 
  array (
    0 => 
    array (
      'contactType' => 'support',
    ),
  ),
  'metadata-set' => 'saml20-sp-remote',
  'AssertionConsumerService' => 
  array (
    0 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
      'Location' => 'https://example.org/adfs/ls/',
      'index' => 0,
      'isDefault' => true,
    ),
    1 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact',
      'Location' => 'https://example.org/adfs/ls/',
      'index' => 1,
    ),
    2 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
      'Location' => 'https://example.org/adfs/ls/',
      'index' => 2,
    ),
  ),
  'SingleLogoutService' => 
  array (
    0 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
      'Location' => 'https://example.org/adfs/ls/',
    ),
    1 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
      'Location' => 'https://example.org/adfs/ls/',
    ),
  ),
  'NameIDFormat' => 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
  'keys' => 
  array (
    0 => 
    array (
      'encryption' => true,
      'signing' => false,
      'type' => 'X509Certificate',
      'X509Certificate' => .... certificate string ...',
    ),
    1 => 
    array (
      'encryption' => false,
      'signing' => true,
      'type' => 'X509Certificate',
      'X509Certificate' => '.... certificate string ...',
    ),
  ),
  'saml20.sign.assertion' => true,
);

saml20-idp-remote

Put the results of this section into /simplesamlphp-1.14.2/metadata/saml20-idp-remote.php

$metadata['http://example.org/adfs/services/trust'] = array (
  'entityid' => 'http://example.org/adfs/services/trust',
  'contacts' => 
  array (
    0 => 
    array (
      'contactType' => 'support',
    ),
  ),
  'metadata-set' => 'saml20-idp-remote',
  'SingleSignOnService' => 
  array (
    0 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
      'Location' => 'https://example.org/adfs/ls/',
    ),
    1 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
      'Location' => 'https://example.org/adfs/ls/',
    ),
  ),
  'SingleLogoutService' => 
  array (
    0 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
      'Location' => 'https://example.org/adfs/ls/',
    ),
    1 => 
    array (
      'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
      'Location' => 'https://example.org/adfs/ls/',
    ),
  ),
  'ArtifactResolutionService' => 
  array (
  ),
  'keys' => 
  array (
    0 => 
    array (
      'encryption' => true,
      'signing' => false,
      'type' => 'X509Certificate',
      'X509Certificate' => '.... certificate string ...',
    ),
    1 => 
    array (
      'encryption' => false,
      'signing' => true,
      'type' => 'X509Certificate',
      'X509Certificate' => '.... certificate string ...',
    ),
  ),
);
  • Finally you will have to provide information to your ADFS administrator. The SimpleSAMLphp library can generate the federation metadata file for you. Here this the link:

https://example.prod.acquia-sites.com/simplesaml/module.php/saml/sp/metadata.php/default-sp?output=xhtml

The federation metadata XML file should look something like this:

<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" entityID="urn:drupal:adfs-saml">
  <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
    <md:KeyDescriptor use="signing">
      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:X509Data>
          <ds:X509Certificate>.... certificate string .....</ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </md:KeyDescriptor>
    <md:KeyDescriptor use="encryption">
      <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:X509Data>
          <ds:X509Certificate> .... certificate string .....</ds:X509Certificate>
        </ds:X509Data>
      </ds:KeyInfo>
    </md:KeyDescriptor>
    <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://example.prod.acquia-sites.com/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp"/>
    <md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://example.prod.acquia-sites.com/simplesaml/module.php/saml/sp/saml2-logout.php/default-sp"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.prod.acquia-sites.com/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp" index="0"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post" Location="https://example.prod.acquia-sites.com/simplesaml/module.php/saml/sp/saml1-acs.php/default-sp" index="1"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://example.prod.acquia-sites.com/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp" index="2"/>
    <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:artifact-01" Location="https://example.prod.acquia-sites.com/simplesaml/module.php/saml/sp/saml1-acs.php/default-sp/artifact" index="3"/>
  </md:SPSSODescriptor>
  <md:ContactPerson contactType="technical">
    <md:GivenName>Minnur</md:GivenName>
    <md:SurName>Yunusov</md:SurName>
    <md:EmailAddress>email@example.com</md:EmailAddress>
  </md:ContactPerson>
</md:EntityDescriptor>

Drupal Configuration

Basic setup

  • Open http://example.prod.acquia-sites.com/admin/config/people/simplesamlphp_auth
  • Check Activate authentication via SimpleSAMLphp option.
  • Configure settings.php. Add the following:
    // SimpleSAMLphp configuration
    # Provide universal absolute path to the installation.
    if (isset($_ENV['AH_SITE_NAME']) && is_dir('/var/www/html/' . $_ENV['AH_SITE_NAME'] . '/simplesamlphp-1.14.2')) {
      $settings['simplesamlphp_dir'] = '/var/www/html/' . $_ENV['AH_SITE_NAME'] . '/simplesamlphp-1.14.2';
    }
    else {
      // Local SAML path.
      if (is_dir(DRUPAL_ROOT . '/../simplesamlphp-1.14.2')) {
        $settings['simplesamlphp_dir'] = DRUPAL_ROOT . '/../simplesamlphp-1.14.2';
      }
    }
  • Modify your .htaccess file by adding the following:
    
      # Copy and adapt this rule to directly execute PHP files in contributed or
      # custom modules or to run another PHP application in the same directory.
      RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$
    + # Allow access to simplesaml paths
    + RewriteCond %{REQUEST_URI} !^/simplesaml
      # Deny access to any other PHP files that do not match the rules above.
      RewriteRule "^.+/.*\.php$" - [F]
  • SimpleSAMLphp_auth module settings. I personally recommend to store configuration for SimpleSAMLphp_auth module settings in settings.php.
    $config['simplesamlphp_auth.settings'] = [
      // Basic settings.
      'activate'                => TRUE, // Enable or Disable SAML login.
      'auth_source'             => 'default-sp',
      'login_link_display_name' => 'Login with your SSO account',
      'register_users'          => TRUE,
      'debug'                   => FALSE,
      // Local authentication.
      'allow' => [
        'default_login'         => TRUE,
        'set_drupal_pwd'        => TRUE,
        'default_login_users'   => '',
        'default_login_roles'   => [
          'authenticated' => FALSE,
          'administrator' => 'administrator',
        ],
      ],
      'logout_goto_url'         => '',
      // User info and syncing.
      // `unique_id` is specified in Transient format, otherwise this should be `UPN`
      // Please talk to your SSO adminsitrators about which format you should be using.
      'unique_id'               => 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn', 
      'user_name'               => 'uid',
      'mail_attr'               => 'mail',
      'sync' => [
        'mail'      => FALSE,
        'user_name' => FALSE,
      ],
    ];

Local authentication

  • Check Allow authentication with local Drupal accounts option. Otherwise you other users won't be able to login to Drupal.
  • Hit Save configuration

User info and syncing

  • Usually we set username as user's email address, if this is the case for you please try add the following:
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
  • Unique identifier for the user
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn
  • User mail address
    • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
  • Hit Save configuration

ADFS Configuration

See Useful Links below. Some of those links contain information on how to configure the ADFS.

Test SimpleSAMLphp and Drupal

  • Try login, use the following URL address to test:
    • https://example.prod.acquia-sites.com/saml_login
  • If you would like to support login via SAML only I would recommend you to replace Login (user/login) path with /saml_login

Troubleshoot

  1. Login works but logout doesn't.
    • Make sure your certificates are generated using correct encryption.
    • Make sure your ADFS configuration is set to use correct encryption. In my case it was set to SHA256 and the certificates were generated using SHA1.
  2. If you're using SHA256 certificates make sure to specify encryption algorithm in config/authsources.php by adding signature.algorithm. See SAML 2.0 Options section.
  3. Get errors on login.
    • Make sure ADFS has correct claim rules. All fields should be in it's own Claim Rule.
  4. Make sure to restart ADFS service every time you make a change.
  5. If login used to work but stopped working after a year/month etc. Most likely the certificate expiration date was set to be a year/month. To solve this issue simply regenerate the certificate. See example above.
  6. If you get Permission Denied message when accessing https://example.prod.acquia-sites.com/simplesaml make sure you properly modified your .htaccess file. More help with the simpleSAMLphp Authentication for Drupal 8 can be found in the module's README file.
  7. In case if you can't access https://example.prod.acquia-sites.com/simplesaml page and you see some technical issue message, try changing session_store from memcache to database.
  8. Can't login and SimpleSAMLphp is not returning attributes? Try clearing Browser cookies and login one more time.

Useful links