If you live in an area with a lot of freight or commuter trains, you may have noticed that trains often have more than one engine powering the cars. Sometimes it is an engine in front and one in back, or in the case of long freight lines, there could be an engine in the middle. This is known as “Distributed power” and is actually a recent engineering strategy. Evenly distributed power allows them to carry more, and carry it more efficiently.1

When it comes to your website, the same engineering can apply. If the Content Management System (CMS) is the only source of power, it may not have enough oomph to load pages quickly and concurrently for many users. Not only that, but a single source of power may slow down innovation and delivery to multiple sources in today’s multi-channel digital ecosystems.

One of the benefits of decoupled platform architecture is that power is distributed more evenly across the endpoints. Decoupled means that the authoring system and the rendering system for site visitors are not the same. Instead of one CMS powering content authoring and page rendering, two systems handle each task discreetly.

Digital properties are ever growing and evolving. While evaluating how to grow your own system, it’s important to know the difference between coupled and decoupled CMS architectures. Selecting the best structure for your organization will ensure you not only get what you want, but what is best for your entire team — editors, developers, designers, and marketers alike.

Bombardier Zefiro vector graphic designed for Vexels

What is a traditional CMS architecture?

In a traditional, or coupled, CMS, the architecture tightly links the back-end content administration experience to the front-end user experience.

Content creation such as basic pages, news, or blog articles are created, managed, and stored along with all media assets through the CMS’s back end administration screens. The back end is also where site developers create and store customized applications and design templates for use by the front-end of the site.

Essentially, the two sides of the CMS are bound within the same system, storing content created by authenticated users and then also being directly responsible for delivering content to the browser and end users (front end).

From a technical standpoint, a traditional CMS platform is comprised of:

  1. A private database-driven CMS in which content editors create and maintain content for the site, generally through some CMS administration interfaces we’re used to (think WordPress or Drupal authoring interfaces)
  2. An application where engineers create and apply design schemas. Extra permissions and features within the CMS give developers more options to extend the application and control the front end output
  3. A public front end that displays published content on HTML pages

What is a decoupled CMS architecture?

Decoupled CMS architecture separates, or decouples, the back-end and front-end management of a website into two different systems — one for content creation and storage, and another for consuming content and presenting it to the user.

In a decoupled CMS, these two systems are housed separately and work independently of the other. Once content is created and edited in the back end, this front-end agnostic approach takes advantage of flexible and fast web services and APIs to deliver the raw content to any front-end system on any device or channel. It is even possible that an authoring system delivers content to more than front-end (i.e. an article is published in the back-end and pushed out to a website as well as a mobile App).

From a technical standpoint, a decoupled CMS platform is comprised of:

  1. A private database-driven CMS in which content editors create and maintain content for the site, generally through the same CMS administration interfaces we’re used to — though it doesn’t have to be2
  2. The CMS provides a way for the front-end application to consume the data. A web-service API — usually in a RESTful manner and in a mashup-friendly format such as JSON — is the most common way
  3. Popular front-end frameworks such as React, VueJS, or GatsbyJS deliver the public visitor experience via a Javascript application rendering the output of the API into HTML

Benefits of decoupled

By moving the responsibility for the user experience completely into the browser, the decoupled model provides a number of benefits:

Push the envelope

Shifting the end-user experience out of the conventions and structures of the back-end allows UX Engineers and front-end masterminds to push the boundaries of the experience. Decoupled development gives front-end specialists full control using their native tools.

This is largely because traditional back-end platforms have been focused on the flexibility of authoring content and less so on the experience of public visitors. Too often the programming experience slows engineers down and makes it more difficult to deliver an experience that “wows” your users.

Need for speed

Traditional CMS structures are bogged down by “out-of-the-box” features that many sites don’t use, causing unnecessary bloat. Decoupled CMS structures allow your web development team to choose only what code they need and remove what they don’t. This leaner codebase can result in faster content delivery times and can allow the authoring site to load more quickly for your editors.

Made to order

Not only can decoupled architecture be faster, but it can allow for richer interactions. The front-end system can be focused on delivering a truly interactive experience in the form of in-browser applications, potentially delivering content without a visitor reloading the page.

The back-end becomes the system of record and “state machine”, but back-and-forth interaction will happen in the browser and in real-time.

Security Guard

Decoupling the back-end from the front-end is more secure. Since the front-end does not expose its connection to the authoring system, it makes the ecosystem less vulnerable to hackers. Further, depending on how the front-end communication is set up, if the back-end goes offline, it may not interrupt the front-end experience.

In it for the long haul

Decoupled architectures integrate easily with new technology and innovations and allow for flexibility with future technologies. More and more, this is the way that digital platform development is moving. Lean back-end only or “flat file” content management systems have entered the market — like Contentful and Cosmic — while server hosting companies are dealing with the needs of decoupled architecture as well.

The best of both worlds

Decoupled architecture allows the best decisions for two very different sets of users. Content editors and authors can continue to use some of the same CMSs they have been familiar with. These CMSs have great power and flexibility for content modelling and authoring workflows, and will continue to be useful and powerful tools. At the same time, front-end developers can get the power and flexibility they need from a completely different system. And your customers can get the amazing user experiences they have come to expect.

The New Age of Content Management Systems

Today’s modern CMS revolution is driving up demand for more flexible, scalable, customizable content management systems that deliver the experience businesses want and customers expect. Separating the front- and back-ends can enable organizations to quicken page load times, iterate new ideas and features faster, and deliver experiences that “wow” your audience.


  1. Great article on the distributed power of trains: Why is there an engine in the middle of that train?
  2. Non-monolithic CMSs have been hitting the market lately, and include products like Contentful, CosmicJS, and Prismic, among others.

Test Driven Development (TDD) facilitates clean and stable code. Drupal 8 has embraced this paradigm with a suite of testing tools that allow a developer to write unit tests, functional tests, and functional JavaScript tests for their custom code. Unfortunately, there is no JavaScript unit testing framework readily available in Drupal core, but don’t fret. This article will show you how to implement JavaScript unit testing.

Why unit test your JavaScript code?

Testing units of code is a great practice, and also guarantees that any future developer doesn’t commit a regression to your logic. Adding unit coverage for JavaScript code is helpful for testing specific logical blocks of code quickly and efficiently without the overhead both in development time and testing time of functional tests.

An example of JavaScript code that would benefit from unit testing would be an input field validator. For demonstration purposes, let’s say you have a field label that permits certain characters, but you want to let the user know immediately if they entered something incorrectly, maybe with a warning message.

Here’s a crude example of a validator that checks an input field for changes. If the user enters a value that is not permitted, they are met with an error alert.

(($, Drupal) => {
  Drupal.behaviors.labelValidator = {
    attach(context) {
      const fieldName = "form.form-class input[name=label]";
      const $field = $(fieldName);
      $field.on("change", () => {
        const currentValue = $field.val();
        if (currentValue.length > 0 && !/^[a-zA-Z0-9-]+$/.test(currentValue)) {
          alert("The value you entered is incorrect!");
        }
      });
    }
  };
})(jQuery, Drupal);

JavaScript

We only allow letters, numbers, and hyphens in this sample validator. We now have a good idea of test data we can create for our test.

Setting up JS Unit Testing

In the world of JavaScript unit testing, Jest has a well-defined feature set, a large community, and is the most popular choice among developers. To begin using Jest, add jest as a development dependency in your favorite manager. Then create a Jest config file, and add your directories for testing. I recommend enabling lcov ; a test coverage reporter that converts test results into local HTML pages.

Writing a Test

We want to test our Drupal behavior, but we need jQuery and the global Drupal object. Have no fear! We can mock all of this. For simplicity’s sake, we can mock both jQuery and Drupal to test the code we want. The point here is to collect the validation logic and run it on our test cases.

There are a couple of different techniques we can use to meet our requirements. You can create a test DOM using a library like JSDOM and require the jQuery library. This gives you the ability to simulate HTML and DOM events. This approach is fine, but our goal is to test our custom validation logic, not to test third-party libraries, or simulate the DOM. Similar to mocking classes and methods in PHPUnit, we can do the same with jest.

Our testing environment is Node, so we can leverage the global object to mock Drupal, jQuery, and even the alert function. Please see Node’s global variable documentation for more information on this object. We can do this in the setup logic of jest with beforeAll:

beforeAll(() => {
  global.alert = jest.fn();
  global.Drupal = {
    behaviors: {}
  };
  global.jQuery = jest.fn(selector => ({
    on(event, callback) {
      validator = callback;
    },
    val() {
      return fieldValue;
    }
  }));
  const behavior = require("label-validator.es6.js");
  Drupal.behaviors.labelValidator.attach();
});

JavaScript

This makes our behavior available to the global Drupal object. We also have mocked jQuery, so we can collect the callback on which we want to run the tests. We run the attach method on the behavior to collect the callback. You may have noticed that we never declared the validator or fieldValue variables; we do this at the top of our test so we have them available in our tests.

// The validation logic we collect from the `change` event.
let validator = () => "";

// The value of the input we set in our tests.
let fieldValue = "";

JavaScript

With the intention of cleanup, we want to unset all the global objects after we have run our tests. In our case, the globals we are mocking do not exist in Node, so it is safe to set them to null. In cases in which we are mocking defined values, we would want to save a backup of that global and then mock it. After we are done testing, we would set the backup back to its corresponding global. There are also many techniques related to mocking globals and even core Node libraries. For an example, check out the documentation on the jest website.

Here is our tear-down logic. We use the jest function afterAll to achieve this:

afterAll(() => {
  global.Drupal = null;
  global.jQuery = null;
  global.alert = null;
});

JavaScript

We need to create an array of values that we know should pass validation and fail validation. We will call them validLabels and invalidLabels, respectively:

/**
 * List of valid labels for the input.
 *
 * @type {string[]}
 */
const validLabels = [
  "123ABVf123",
  "123",
  "AB",
  "1",
  "",
  "abcdefghijklmnop12345678910",
  "ab-3-cd"
];

/**
 * List of invalid labels for the input.
 *
 * @type {string[]}
 */
const invalidLabels = [
  "!@#fff",
  "test test",
  "(123)",
  "ABCDEF123!",
  "^Y1",
  " ",
  "'12346'",
];

JavaScript

Finally, we are ready to start writing our tests. We can use jest’s provided test function, or we can use the “describe it” pattern. I prefer the “describe it” pattern because you can provide detailed information on what you are testing and keep it in the same test scope.

Firstly, we want to test our valid data, and we know that these values should never trigger an alert. We will call the validator on each test value and set the expectation that the alert function is never called. But before we write the test, we want to make sure to clear all our mocks between tests to prevent mock pollution. We can achieve this with beforeEach:

beforeEach(() => {
  jest.clearAllMocks();
});

JavaScript

After writing our valid data test, we will write our invalid data test. This test should expect an alert for each invalid value sent. Putting it all together we have:

describe("Tests label validation logic", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });
  it("valid label test", () => {
    validLabels.forEach(value => {
      fieldValue = value;
      validator();
    });
    expect(global.alert.mock.calls.length).toBe(0);
  });
  it("invalid label test", () => {
    invalidLabels.forEach(value => {
      fieldValue = value;
      validator();
    });
    expect(global.alert.mock.calls.length).toBe(invalidLabels.length);
  });
});

JavaScript

After writing our tests, we can check our coverage and see we have hit 100%!

Visual output of Jest using the lcov test reporter

Jest is extremely flexible and has a large ecosystem. There are many different ways we could have achieved the above results; hopefully this gives you some useful ideas on how to unit test your javascript code.


The entire sample Jest test:

/* global test expect beforeEach afterAll beforeAll describe jest it */
// The validation logic we collect from the `change` event.
let validator = () => "";
// The value of the input we set in our tests.
let fieldValue = "";
// the setup function where we set our globals.
beforeAll(() => {
  global.alert = jest.fn();
  global.Drupal = {
    behaviors: {}
  };
  global.jQuery = jest.fn(selector => ({
    on(event, callback) {
      validator = callback;
    },
    val() {
      return fieldValue;
    }
  }));
  const behavior = require("label-validator.es6.js");
  Drupal.behaviors.labelValidator.attach();
});
// Global tear down function we use to remove our mocks.
afterAll(() => {
  global.Drupal = null;
  global.jQuery = null;
  global.alert = null;
});
/**
 * List of valid labels for the input.
 *
 * @type {string[]}
 */
const validLabels = [
  "123ABVf123",
  "123",
  "AB",
  "1",
  "",
  "abcdefghijklmnop12345678910",
  "ab-3-cd"
];
/**
 * List of invalid labels for the input.
 *
 * @type {string[]}
 */
const invalidLabels = [
  "!@#fff",
  "test test",
  "(123)",
  "ABCDEF123!",
  "^Y1",
  " ",
  "'12346'",
];
// The tests.
describe("Tests label validation logic", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });
  it("valid label test", () => {
    validLabels.forEach(value => {
      fieldValue = value;
      validator();
    });
    expect(global.alert.mock.calls.length).toBe(0);
  });
  it("invalid label test", () => {
    invalidLabels.forEach(value => {
      fieldValue = value;
      validator();
    });
    expect(global.alert.mock.calls.length).toBe(invalidLabels.length);
  });
});

JavaScript

Resources

This post will assume you have already completed the base setup of enabling Layout Builder and added the ability to manage layouts to one of your content types. If you are not to this point check out Drupal.orgs documentation on layout builder or this article by Tyler Fahey which goes over setup and some popular contrib module enhancements.

As we mentioned in part 1 of this series, you should expect a little DIY with Layout Builder. So far the best way we have found to theme Layout Builder is by creating a custom module to provide our own custom layouts and settings. By defining custom layouts in a custom module we get the ability to control each layout’s markup as well as the ability to add/remove classes based on the settings we define.

Writing the custom layout module

Setup the module

Start by creating your custom module and providing the required .info.yml file.

demo_layout.info.yml:

name: Demo Layout
description: Custom layout builder functionality for our theme.
type: module
core: 8.x
package: Demo

dependencies:
  - drupal:layout_builder

YAML

Remove default core layouts

Layout Builder comes with some standard layouts by default. There’s nothing wrong with these, but generally for our clients, we want them only using our layouts. This hook removes those core layouts, leaving only the layouts that we will later define:

demo_layout.module

/**
 * Implements hook_plugin_filter_TYPE__CONSUMER_alter().
 */
function demo_layout_plugin_filter_layout__layout_builder_alter(array &$definitions): void {
  // Remove all non-demo layouts from Layout Builder.
  foreach ($definitions as $id => $definition) {
    if (!preg_match('/^demo_layout__/', $id)) {
      unset($definitions[$id]);
    }
  }
}

PHP

Register custom layouts and their regions

The next step is to register the custom layouts and their respective regions. This process is well documented in the following drupal.org documentation: https://www.drupal.org/docs/8/api/layout-api/how-to-register-layouts

For this particular demo module we are going to define a one column and a two column layout. These columns will be able to be sized later with the settings we provide.

demo_layout.layouts.yml

demo_layout__one_column:
  label: 'One Column'
  path: layouts/one-column
  template: layout--one-column
  class: Drupal\demo_layout\Plugin\Layout\OneColumnLayout
  category: 'Columns: 1'
  default_region: first
  icon_map:
    - [first]
  regions:
    first:
      label: First

demo_layout__two_column:
  label: 'Two Column'
  path: layouts/two-column
  template: layout--two-column
  class: Drupal\demo_layout\Plugin\Layout\TwoColumnLayout
  category: 'Columns: 2'
  default_region: first
  icon_map:
    - [first, second]
  regions:
    first:
      label: First
    second:
      label: Second

YAML

Pay close attention to the path, template, and class declarations. This determines where the twig templates and their respective layout class get placed.

Creating the base layout class

Now that we have registered our layouts, it’s time to write a base class that all of the custom layouts will inherit from. For this demo we will be providing the following settings:

However, there is a lot of PHP to make this happen. Thankfully for the most part it follows a general pattern. To make it easier to digest, we will break down each section for the Column Width setting only and then provide the entire module at the end which has all of the settings.

src/Plugin/Layout/LayoutBase.php

<?php
  declare(strict_types = 1);

  namespace Drupal\demo_layout\Plugin\Layout;

  use Drupal\demo_layout\DemoLayout;
  use Drupal\Core\Form\FormStateInterface;
  use Drupal\Core\Layout\LayoutDefault;

  /**
   * Provides a layout base for custom layouts.
   */
  abstract class LayoutBase extends LayoutDefault {

  }

PHP

Above is the layout class declaration. There isn’t a whole lot to cover here other than to mention use Drupal\demo_layout\DemoLayout;. This class isn’t necessary but it does provide a nice one-stop place to set all of your constant values. An example is shown below:

src/DemoLayout.php

<?php

declare(strict_types = 1);

namespace Drupal\demo_layout;

/**
 * Provides constants for the Demo Layout module.
 */
final class DemoLayout {

  public const ROW_WIDTH_100 = '100';

  public const ROW_WIDTH_75 = '75';

  public const ROW_WIDTH_50 = '50';

  public const ROW_WIDTH_25 = '25';

  public const ROW_WIDTH_25_75 = '25-75';

  public const ROW_WIDTH_50_50 = '50-50';

  public const ROW_WIDTH_75_25 = '75-25';

}

PHP

The bulk of the base class logic is setting up a custom settings form using the Form API. This form will allow us to formulate a string of classes that get placed on the section or to modify the markup depending on the form values. We are not going to dive into a whole lot of detail as all of this is general Form API work that is well documented in other resources.

Setup the form:

/**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state): array {

    $columnWidths = $this->getColumnWidths();

    if (!empty($columnWidths)) {
      $form['layout'] = [
        '#type' => 'details',
        '#title' => $this->t('Layout'),
        '#open' => TRUE,
        '#weight' => 30,
      ];

      $form['layout']['column_width'] = [
        '#type' => 'radios',
        '#title' => $this->t('Column Width'),
        '#options' => $columnWidths,
        '#default_value' => $this->configuration['column_width'],
        '#required' => TRUE,
      ];
    }

    $form['#attached']['library'][] = 'demo_layout/layout_builder';

    return $form;
  }

 /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $this->configuration['column_width'] = $values['layout']['column_width'];
  }

 /**
   * Get the column widths.
   *
   * @return array
   *   The column widths.
   */
  abstract protected function getColumnWidths(): array;

PHP

Finally, we add the build function and pass the column width class:

/**
   * {@inheritdoc}
   */
  public function build(array $regions): array {
    $build = parent::build($regions);

    $columnWidth = $this->configuration['column_width'];
    if ($columnWidth) {
      $build['#attributes']['class'][] = 'demo-layout__row-width--' . $columnWidth;
    }

    return $build;
  }

PHP

Write the column classes

Now that the base class is written, we can write column-specific classes that extend it. These classes are very minimal since most of the logic is contained in the base class. All that is necessary is to provide the width options for each individual class.

src/Plugin/Layout/OneColumnLayout.php

<?php

declare(strict_types = 1);

namespace Drupal\demo_layout\Plugin\Layout;

use Drupal\demo_layout\DemoLayout;

/**
 * Provides a plugin class for one column layouts.
 */
final class OneColumnLayout extends LayoutBase {

  /**
   * {@inheritdoc}
   */
  protected function getColumnWidths(): array {
    return [
      DemoLayout::ROW_WIDTH_25 => $this->t('25%'),
      DemoLayout::ROW_WIDTH_50 => $this->t('50%'),
      DemoLayout::ROW_WIDTH_75 => $this->t('75%'),
      DemoLayout::ROW_WIDTH_100 => $this->t('100%'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function getDefaultColumnWidth(): string {
    return DemoLayout::ROW_WIDTH_100;
  }
}

PHP

src/Plugin/Layout/TwoColumnLayout.php

<?php

declare(strict_types = 1);

namespace Drupal\demo_layout\Plugin\Layout;

use Drupal\demo_layout\DemoLayout;

/**
 * Provides a plugin class for two column layouts.
 */
final class TwoColumnLayout extends LayoutBase {

  /**
   * {@inheritdoc}
   */
  protected function getColumnWidths(): array {
    return [
      DemoLayout::ROW_WIDTH_25_75 => $this->t('25% / 75%'),
      DemoLayout::ROW_WIDTH_50_50 => $this->t('50% / 50%'),
      DemoLayout::ROW_WIDTH_75_25 => $this->t('75% / 25%'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function getDefaultColumnWidth(): string {
    return DemoLayout::ROW_WIDTH_50_50;
  }
}

PHP

We can now check out the admin interface and see our custom form in action.

One column options:

Two column options:

Add twig templates

The last step is to provide the twig templates that were declared earlier in the demo_layout.layouts.yml file. The variables to be aware of are:

src/layouts/one-column/layout–one-column.html.twig

{#
/**
 * @file
 * Default theme implementation to display a one-column layout.
 *
 * Available variables:
 * - content: The content for this layout.
 * - attributes: HTML attributes for the layout <div>.
 * - settings: The custom form settings for the layout.
 *
 * @ingroup themeable
 */
#}

{%
  set row_classes = [
    'row',
    'demo-layout__row',
    'demo-layout__row--one-column'
  ]
%}

{% if content %}
  <div{{ attributes.addClass( row_classes|join(' ') ) }}>
    <div {{ region_attributes.first.addClass('column', 'column--first') }}>
      {{ content.first }}
    </div>
  </div>
{% endif %}

Twig

src/layouts/two-column/layout–two-column.html.twig

{#
/**
 * @file
 * Default theme implementation to display a two-column layout.
 *
 * Available variables:
 * - content: The content for this layout.
 * - attributes: HTML attributes for the layout <div>.
 * - settings: The custom form settings for the layout.
 *
 * @ingroup themeable
 */
#}

{# Get the column widths #}
{% set column_widths = settings.column_width|split('-') %}

{%
  set row_classes = [
    'row',
    'demo-layout__row',
    'demo-layout__row--two-column'
  ]
%}

{% if content %}
  <div{{ attributes.addClass( row_classes|join(' ') ) }}>

    {% if content.first %}
      <div {{ region_attributes.first.addClass('column', 'column--' ~ column_widths.0, 'column--first') }}>
        {{ content.first }}
      </div>
    {% endif %}

    {% if content.second %}
      <div {{ region_attributes.second.addClass('column', 'column--' ~ column_widths.1, 'column--second') }}>
        {{ content.second }}
      </div>
    {% endif %}

    </div>
  </div>
{% endif %}

Twig

Notice settings.column_width was passed with a string: 75-25. We need to split it and place each value on our column which results in the following output.

<div class="demo-layout__row-width--75-25 row demo-layout__row demo-layout__row--two-column ">
  <div class="column column--75 column--first"></div>
  <div class="column column--25 column--second"></div>
</div>

HTML

Since these are custom classes, and we haven’t written any CSS, these columns do not have any styling. Depending on your preference, you can implement your own custom column styles or wire up a grid framework such as Bootstrap in order to get the columns to properly size themselves.

Wrapping it up

You should be at a point where you have an idea of how to create custom settings in order to theme layout builder sections. You can take this method and extend it however you need to for your particular project. There’s no definitive best way to do anything in the world of web development, and Layout Builder is no exception to that rule. It’s a great addition to Drupal’s core functionality, but for larger sites, it likely won’t be and shouldn’t be the only way you handle layout. Much like Drupal itself though, as more and more people use it, Layout Builder will only become stronger, more robust, more fully-featured, and better documented. If it doesn’t seem like a good fit for you right now, it may become a better fit as it grows. If it does seem like a good fit, be ready to get your hands dirty!

The full demo layouts module with all of the custom settings is available here: https://github.com/oomphinc/layout-builder-demo/tree/master/moduleexamples/demolayout

Maintaining logins for numerous platforms can become a headache over time. This is especially the case for an organization where members join and leave often. On- and off-boarding can become a hassle to manage.

For one of our clients, we needed a better way to control application privileges. If a new team member was starting, our team would manually create new accounts. Then if someone left their company or ours, we would manually remove their account from the infrastructure. This was not only an extra set of tasks which require time to accomplish, but also a potential point of failure where forgetting to remove a former team member’s access.

Single-sign-on to the Rescue

Single-sign-on (SSO) is something that almost all internet users have encountered, whether or not they know it. Logging in to Instagram with your Facebook account, or signing in to any number of Android apps with your Google account are some easy examples.

To the user, this is much easier than creating a new account and filling in yet another registration form — and keeping track of one more password. In these cases, your identity is maintained by one entity (Facebook or Google) and used to populate your account information on a service provider (Instagram or Android).

SSO as a De-provisioning Tool

In a business environment, the relationship between the Identity Provider (IdP) and Service Provider (SP) has a very useful feature — if the account is removed on the IdP’s side, that user will no longer be able to access the SP application. This makes de-provisioning users a single-step process.

At Oomph, we use Google’s G Suite for email, calendars, and their productivity Apps. If we could use our Google accounts to sign in to, say, Evernote, why couldn’t we also use our Google identity to control access to our client projects?

Getting Google and Okta to Shake Hands

Our client in this example had been using <a href=”//www.okta.com”target=”_blank” rel=”noopener noreferrer” title=”Opens in a new window”>Okta as their SSO provider. In our experience, they have been pretty great to work with. Through Okta, we can control access to various entities — from logins for end users on the web application, to client access to their CMS, to access control for the GitHub organization where the code repository is hosted.

Up until this point however, we had always created accounts and signed in to Okta manually — like cave people. There had to be a better way. By using Google as our own Identity Provider to manage access to Okta, and through that, our client’s environments, we could have a single source of truth and much easier provisioning/deprovisioning.

Less than Stellar Documentation

The internet and Google have amazingly detailed and accurate tutorials for a lot of things, but not everything. As is the case sometimes, the documentation on these matters was thin or confusing or just plain wrong. At points we found that we started reading about how to set Google as an IdP, only to later realize that the documents appeared to discuss the reverse relationship — using Okta as an IdP for Google Apps.

After some trial and error, we were able to get a successful handshake between the two. We may have created the digital equivalent of Scrödinger’s cat in the process, but that’s a story for another time. Here is how we did it.

How to Set up Google as an IdP with Okta SSO

Okta does have a Google-specific Identity Provider type, however, it led us down a path where we ended up with an Identity Provider which both existed and didn’t exist at the same time (the aforementioned Scrödinger’s cat). As such, we found the easiest method was to use a standard SAML 2.0 Identity Provider. These exact steps are for setting up a connection to Okta — these basic principles should also apply to any SSO service provider which can use SAML 2.0. With that background, let’s get started:

In Okta, navigate to the admin panel, and from here, select Identity Providers under the Security tab

Press “Add Identity Provider” and then “Add SAML 2.0 IdP” from the dropdown

Here you will have a number of options to configure, and many will depend on your own setup. Some important ones are:

  1. Name: give your Identity Provider an appropriate name
  2. IdP Username: typically idpuser.subjectNameId will suit your needs
    1. An extra hurdle we encountered for our own use case was the fact that at Oomph, some years ago we migrated to a new domain — while we typically use “oomphinc.com” these days, all of our previous accounts were set up with a legacy email root in place. Google still sees our legacy domain as the primary, and as such, would attempt to provision new (incorrect) users for us. Many fields in Okta allow the use of their expression engine. Leveraging this, we were able to set our IdP Username field as follows: String.replace(idpuser.subjectNameId, "legacy", "preferred") meaning that while Google is sending “jdoe@legacy.com”, the Okta account will be created as “jdoe@preferred.com”
  3. Match against: Okta Username or email
  4. Profile Master: This will depend on the use case. It refers back to the idea that if a user’s information is changed in Google, their Okta profile will be updated to match. If left unchecked, the user may update their information in Okta without it being overwritten on their next login
  5. Group assignments: Whether to assign the user to Okta groups — either a static group (or groups) for all users. With the option “Full sync of groups” and an attribute name as well as a list of groups in the Group Filter, an attribute can be sent by the IdP containing the name of the group a user should be assigned to if it is in the Group Filter list.

For now, leave the Okta IdP configuration screen open, and it is time to hop over to your Google admin.google.com

Navigate to Apps -> SAML Apps

Add a new app with the “+” in the bottom right corner of the screen

In the modal, select “Setup my own custom app” at the bottom of the pop up

Copy SSO URL, Entity ID and download the certificate

Return to the Okta setup page, and enter the values from G Suite for the IdP Single Sign-On URL (SSO URL), IdP Issuer URI (Entity ID), and upload the certificate downloaded from Google

Press “Add Identity Provider” in Okta

On the Identity Provider screen in Okta, expand the new Identity Provider, and copy the Assertion Consumer Service URL and Audience URI values

Return to Google, enter a name for your application, and optionally add a description and logo, then navigate to the next step

On this step, enter the ACS URL (Assertion Consumer Service URL from Okta) and Entity ID (Audience URI from Okta)

Leave the Start URL blank

Check the Signed Response box

Leave the Name ID values as defaults

Set Name ID Format to “EMAIL”

Press “Next” to move on to attribute mapping. This will depend entirely on your own needs. The left column will be the field names expected by your Okta configuration. This is the profile data which will be sent from Google to Okta when creating or updating the user account in Okta

At this point you should have the basics configured to connect to Okta with Google as an Identity Provider. Press “Finish” to save the configuration in Google. From here you can grant access to users in your Google organization. Your app should now appear at gsuite.google.com/dashboard and selecting your new application should bring you to Okta and sign you in automatically, granting access to any Okta managed applications.

Good Luck with your Configuration

The convenience of the connection between Google and Okta for our organization and our client’s can’t be denied. We are so glad to have this set up. But it took some doing, particularly when the documentation we were able to find was incomplete. We hope this info can help your organization get these two talking to each other.

Can your organization benefit from a fine-tuned SSO or SAML connection? Drop us a line with questions or requests.

Note: This blog post is NOT about Voting in Iowa and an App that failed to do its job. But if anyone wants an opinion on how that App should have been built and how much it should have cost, drop us a line 🙂

The great thing about the open source community around Drupal is the range of complex features that already exist. Sometimes, though, the documentation on how to use those pre-built pieces of functionality is lacking. That’s a situation we found ourselves in recently with Drupal’s Voting API.

We found a great tutorial for implementing the Voting API without any customization. Drupalize.Me also has helpful instructional video and text. But if you want to extend the module programmatically, there is very little material online to help guide the way.

The Problem to Solve

Oomph needed to launch a national crowd-sourcing contest for a long-time client. We had to provide visitors with a chance to submit content, and we needed to enable a panel of judges to vote on that content and determine a winner. But behind the scenes, we had to add functionality to enable voting according to specific criteria that our client wanted to enforce. For moderation, there would be an admin page that displays all entries, the scores from all judges, and the ability to search and sort.

Oh, and we had to turn it around in three months — maybe we should have led with that requirement. 😊

Architecting the Solution

Drupal 8 was a natural fit for rendering forms to the user and collecting the input for judging. A few contributed modules got us closer to the functionality we wanted — webformwebformcontentcreatorfivestarvotingapi, and votingapi_widgets.

The robust framework of the Voting API has been around since Drupal 4, so it was a great foundation for this project. It made no sense to build our own voting system when one with a stable history existed. Customizing the way that the API worked put up some roadblocks for our engineering team, however, and the lack of documentation around the API did not help. We hope that by sharing what we learned along the way, we can support the community that maintains the Voting API.

The Lifecycle of the Contest

Submission

The submission form we created is, at its base, a multi-step Webform. It is divided into three pages of questions and includes input for text, images, and video. The form wizard advances users from page to page, with the final submittal kicking off additional processes. A visitor can save an incomplete submission and return to it later. The Webform module contains all of these features, and it saved our team a lot of work.

Example of a multi-step webform

After pressing Submit, there is some custom pre-processing that happens. In this case, we needed to execute a database lookup for one of the submitted fields. Below is an example of code that can be added to a custom module to look up a value and save it to the recently submitted webform. This is a basic code template and won’t include sanitizing user input or all the checks you might need to do on a production-level implementation.

The code snippet and other examples from this blog post are available on Github as a Gist: gist.github.com/bookworm2000/cd9806579da354d2dd116a44bb22b04c.

use \Drupal\webform\Entity\WebformSubmission;

/**
 * Implements hook_ENTITY_TYPE_presave().
 */
function mymodule_webform_submission_presave(WebformSubmission $submission) {
 // Retrieve the user input from the webform submission.
 $submitted_data = $submission->getData();
 $zipcode = $submitted_data['zipcode'];

  // Retrieve the state name from the database custom table.
  // This is calling a service included in the mymodule.services.yml file.
  $state_lookup = \Drupal::service('mymodule.state_lookup');
  $state_name = $state_lookup->findState($zipcode);

  if ($state_name) {
    // Update the webform submission state field with the state name.
    $submitted_data['state'] = $state_name;
    $submission-&amp;gt;setData($submitted_data);
  }
}

PHP

Content Entry

The Webform Content Creator module allows you to map fields from a submission to a specified content type’s fields. In our case, we could direct all the values submitted by the contestant to be mapped to our custom Submission content type — every time a user submitted an entry, a new node of content type Submission was created, with all the values from the webform populated.

id: dog_contest_submission_content_creator
title: 'Dog Contest Submission Content Creator'
webform: mycustomform
content_type: dog_contest_submission
field_title: 'Dog Contest - [webform_submission:values:name]'
use_encrypt: false
encryption_profile: ''
elements:
  field_email:
    type: false
    webform_field: email
    custom_check: false
    custom_value: ''
  field_dog_breed:
    type: false
    webform_field: dog_breed
    custom_check: false
    custom_value: ''
  field_dog_name:
    type: false
    webform_field: dog_name
    custom_check: false
    custom_value: ''
  field_dog_story:
    type: false
    webform_field: the_funniest_thing_my_dog_ever_did
    custom_check: false
    custom_value: ''

YAML

Voting

The main technical obstacle was that there were no voting widgets that fit the project requirements exactly:

The Fivestar contributed module is the module used most often on Drupal sites to implement the Voting API. It is extensible enough to allow developers or designers to add custom icons and customized CSS. The basic structure of the widget is always the same, unfortunately, and was not suitable for our contest. The available widget options are default stars, small stars, small circles, hearts, flames, or “Craft,” as shown:

The Fivestar module voting options

We enabled the Fivestar module and opted to override the base widget in order to implement a new one.

Technical Deep Dive

After enabling the Voting API Widgets module, we could now add our voting field to the submission content type. (Add a field of type “Fivestar Rating” if you want to try out the Fivestar module’s custom field.)

Note: we used the Voting API Widgets 8.x-1.0-alpha3 releaseThere is now already an alpha5 release that introduces some structural (breaking!) changes to the base entity form that conflict with our custom solution. Job security!

Select a new field of type “Voting API”

In the field settings for the new Voting API field, the only plugin choices available are plugins from the Fivestar module.

Select a vote type and plugin for the Score

We could not use those plugins for our use case, so we went ahead and built a custom plugin to extend the VotingApiWidgetBase class. For the purpose of this example, let’s call it “SpecialWidget.”

Additionally, we needed to have the ability to set defaults for the voting widget. For that, we had to add a custom form that extends the BaseRatingForm class. The example class here is called “TenRatingForm,” since the voting widget will display a 1-to-10 dropdown list for the judges.

The file structure for the directories and files in the custom module reads like this:

modules
- contrib
- custom
-- mymodule
--- src
---- Form
----- TenRatingForm.php
---- Plugin
----- votingapi_widget
------ SpecialWidget.php
---- StateLookup.php
--- mymodule.info.yml
--- mymodule.module
--- mymodule.services.yml

Let’s look at the SpecialWidget.php file in more detail. It is fairly straightforward, composed of a namespace, class declaration, and 2 inherited methods.

The namespace references the custom module. The annotation sets the widget values list, so you can adjust easily to add your own content. It is critical to include a “use” statement to incorporate the VotingApiWidgetBase class, or else the buildForm( and getStyles() methods from the parent class will not be found, and angry error messages will show up when you try to use your new custom voting widget.

namespace Drupal\mymodule\Plugin\votingapi_widget;
use Drupal\votingapi_widgets\Plugin\VotingApiWidgetBase;
    /**
     * Custom widget for voting.
     *
     * @VotingApiWidget(
     *   id = "special",
     *   label = @Translation("Special rating"),
     *   values = {
     *    1 = @Translation("1"),
     *    2 = @Translation("2"),
     *    3 = @Translation("3"),
     *    4 = @Translation("4"),
     *    5 = @Translation("5"),
     *    6 = @Translation("6"),
     *    7 = @Translation("7"),
     *    8 = @Translation("8"),
     *    9 = @Translation("9"),
     *    10 = @Translation("10"),
     *   },
     * )
     */

PHP

The other important section to point out is defining the class and the buildForm() method. The SpecialWidget class will now inherit the methods from VotingApiWidgetBase, so you do not need to copy all of them over.

class SpecialWidget extends VotingApiWidgetBase {
  /**
   * Vote form.
   */
  public function buildForm($entity_type, $entity_bundle, $entity_id, $vote_type, $field_name, $style, $show_results, $read_only = FALSE): array {
    $form = $this->getForm($entity_type, $entity_bundle, $entity_id, $vote_type, $field_name, $style, $show_results, $read_only);
    $build = [
      'rating' => [
        '#theme' => 'container',
        '#attributes' =&gt; [
          'class' => [
            'votingapi-widgets',
            'special',
            ($read_only) ? 'read_only' : '',
          ],
        ],
        '#children' =&gt; [
          'form' => $form,
        ],
      ],
    ];
    return $build;
  }
}

PHP

One additional crucial step is overriding the order of operations with which the entity builds occur. The Voting API Widgets module takes precedence over custom modules, so it is necessary to strong-arm your module to the front to be able to see changes. Ensure that the custom plugin is being called by the Voting API Widgets module, and then also ensure that the mymodule_entity_type_build() in the custom module takes precedence over the votingapi_widgets_entity_type_build() call. These functions go in the mymodule.module file.

/**
 * Implements hook_entity_type_build().
 */
function mymodule_entity_type_build(array &amp;$entity_types) {
  $plugins = \Drupal::service('plugin.manager.voting_api_widget.processor')-&gt;getDefinitions();
  foreach ($plugins as $plugin_id =&gt; $definition) {
    // Override the votingapi_widgets form class for the custom widgets.
    if ($plugin_id === 'special') {
      $entity_types['vote']->setFormClass('votingapi_' . $plugin_id,
        'Drupal\mymodule\Form\TenRatingForm');
    }
  }
}

/**
 * Implements hook_module_implements_alter().
 */
function mymodule_module_implements_alter(array &amp;$implementations, string $hook) {
  if ($hook === 'entity_type_build') {
    $group = $implementations;
    $group = $implementations['mymodule'];
    unset($implementations['mymodule']);
    $implementations['mymodule'] = $group;
  }
}

PHP

After adding the custom plugin to the custom module, the option to select the widget will be available for use by the Voting API field. (After clearing all caches, of course.) The field here is called Score.

Select a vote type and plugin for the Score

Adjusting the style of the widget can be done in the “Manage Display” section for the content type:

Choose a display option for the VotingAPI Formatter in Manage Display

And here is how it looks when the voting field has been added to the Submission content type:

The last piece of the puzzle are the permissions. As with most custom features, your existing user roles and permissions will need to be configured to allow users to vote, change votes, clear votes, etc… — all the features that the Voting API Widgets module provides. Unless the voting will be done by authenticated users, most of the boxes should be checked for anonymous users — the general public.

The Permissions screen and the VotingAPI options

The judges can now vote on the node. A judge can vote as many times as they want, according to our client’s specs. Each vote will be saved by the Voting API. Depending on how you want to use the voting data, you can opt to display the most recent vote, the average of all votes, or even the sum of votes.

All voting data will be entered into the votingapi_vote table of your Drupal database by the Voting API module.

SQL Create statement for the Voting API showing how the data is stored

Success!

To Wrap it All Up

We hope you can appreciate the benefit of leveraging open source software within the Drupal ecosystem to power your projects and campaigns. Between the Voting API and Voting API Widgets modules alone, there were over 5,000 lines of code that our engineers did not have to write. Extending an existing codebase that has been designed with OOP principles in mind is a major strength of Drupal 8.

While not formally decoupled, we were able to separate the webform and submission theming structurally from the voting functionality so that our designers and engineers could work side-by-side to deliver this project. Credit to our team members Phil Frilling and Ben Holt for technical assists. The client rated us 10 out of 10 for satisfaction after voting was formally opened in a live environment!