Drupal Commerce Blog

What's happening in the world of Drupal Commerce.

Using OpenID Connect for Single Sign-On with Drupal

At Commerce Guys we provide a varied range of services, including our cloud PaaS Platform.sh, this Drupal Commerce community website, support, and the Commerce Marketplace.

Our users may need to log in to any of these services, and sometimes several at the same time. So we needed to have a shared authentication system, a way of synchronizing user accounts, and single sign-on (SSO) functionality.

After a lot of research on the existing methods, such as CAS, we found that there was no generic open-source solution which would cover all of our current needs and would also allow us to grow and scale in the future when adding new features or applications.

We decided to implement the OAuth 2.0 and OpenID Connect protocols, which were designed to be flexible, yet simple and standardized - exactly what we wanted.

Naturally, as a result of our work, we published three Drupal modules: OAuth2 Server, OpenID Connect, and OpenID Connect SSO.

In this post I will describe how these modules are designed to work. Each project has a documentation page on Drupal.org, where you can find examples and set-up instructions: links are at the end of the post.

Why OAuth 2.0?

OAuth 2.0 – as its specification describes – is a framework which allows a third-party application to authorize with a server. The application can authorize on behalf of a user, or (in the user’s absence) on its own behalf. There are many ways of using OAuth2, and there are implementations of it in many programming languages.

As well as server-side applications (such as the Drupal clients described below), OAuth2 is a good choice for client-side applications: it supports ‘public’ clients that cannot store tokens securely, and it has a fast ‘implicit flow’ (using URL fragments) for browser-based clients.

As such it would be useful for ‘headless Drupal’ sites which allow users to log in via a separate JavaScript-based front end, and/or via native mobile apps.

Platform.sh has a Web UI, written using the Angular.js framework, and a command-line interface (written in PHP, based on Symfony Console). Both use the flexibility of OAuth2 to allow users to log in with their Marketplace credentials.

Why OpenID Connect?

OpenID Connect (OIDC) is an identity protocol built on OAuth2. An application using OpenID Connect may verify a user's identity with an OAuth2 server via a signed token, and it can also obtain profile information about the user. The OpenID Connect protocol provides a standardized way of verifying and exchanging user information, which means that client applications can easily give users a choice of multiple login providers.

You can use OpenID Connect to let users log in with other services, such as Google. The OpenID Connect module already provides a default ‘provider’ plugin for Google, and it is easy to configure new clients via the UI or to write new plugins in code.

The authorization server

The Drupal 'server' website holds information about user accounts centrally. It has the following modules installed:

Conveniently, OpenID Connect server functionality is built into the OAuth2 module. If you wanted to implement shared authentication without SSO, then the server site would only need the OAuth2 Server module and library.

The server must be accessible over HTTPS with a valid SSL certificate.

See the OAuth2 Server documentation for how to configure the module.

The clients

The Drupal 'client' websites do not allow users to register directly: their user accounts are created during the login process.

When a user clicks to log in on a client site, s/he is redirected to the server's login page. After the user logs in, s/he is redirected back to the client. The client site receives an ID token and an access token, with which it can then request further information about the user (in the background).

The 'client' site has the following modules installed:

By default, OpenID Connect synchronizes the user name, email address, and timezone with the server. The information is requested by the client site whenever the user logs in.

You can configure OpenID Connect to synchronize more data - for example profile pictures, and other user fields. Each piece of user information that the server provides is called a ‘user claim’. Claims can be requested individually, or as part of a ‘scope’.

OpenID Connect, by default, does not synchronize any role or permission information; its primary purpose is identity, not authorization. Client sites may each have very different roles or permissions for their users.

The clients themselves must be trusted and recognized by the server. Each client must have a ‘client ID’ and a ‘client secret’. The ID and secret combination is stored on both the client and the server, and it is used to validate exchanged tokens.

See the OpenID Connect documentation for how to configure clients.

Single Sign-On

Single Sign-On (SSO) is an optional extra for a shared authentication system. It allows users to be automatically logged in to all of your network's websites, when they log in to any one site. Perhaps even more importantly, it ensures that when users log out of any one site, they are logged out of all the network’s sites.

The OpenID Connect SSO module achieves this through a system of redirects, in order to share cookies across domain names. It is the same approach used by Google Accounts.

Drupal + OpenID Connect SSO flow chart

This is a summary of the typical SSO flow:

  • The user, or “Resource Owner” (for simplicity, let’s call her Alice) visits Client 1 (example-client1.com), and clicks ‘Log in’.
  • She is redirected to the login page on the Server (example-server.com), where she enters her credentials. She is then redirected back to Client 1, as usual. So far, this is the normal OAuth2 flow.
  • The OpenID Connect SSO module redirects Alice’s browser to an ‘SSO script’, a single standalone PHP script which is hosted on multiple domains.
  • The first address of this SSO script is <example-client2.com/sso.php>. It then redirects the browser to all of the domains in the ‘network’: <example-client3.com/sso.php>, <example-client4.com/sso.php>, and <example-client5.com/sso.php>.
  • At each domain, the SSO script was able to set a ‘login’ cookie in the browser. The cookie simply denotes that Alice should be logged in.
  • Alice visits Client 2 (example-client2.com), or any of the other client sites. Because a login cookie is set for that domain, she is immediately redirected to the Server, where she is already logged in, so she is redirected back again to the client site, with the proper authentication.
  • When Alice logs out, on any of these network sites, the same process happens in reverse. She is redirected to the SSO script again, which unsets any ‘login’ cookies and sets a new ‘logout’ cookie, for all of the domains involved.

The SSO script’s redirect process is invisible to Alice, and it’s fast, because this is just a standalone PHP script (avoiding Drupal’s bootstrap). On each domain, the script need only set a cookie and issue a redirect. These cookies contain no sensitive information - no tokens, no timestamps, no user IDs - they only signify Alice’s intent to be logged in (or out). As such, they are a simple and secure way of implementing SSO.

See the OpenID Connect SSO documentation for how to set up the script and the module.

More information

If you want to set up OpenID Connect for your own Drupal site(s), this is a recording of a recent  Drupal Commerce Tech Talk (by Nick Vahalik), which is a great start: https://www.youtube.com/watch?v=3K-2IfC8lpg

Further links:

Patrick Dawkins
Posted: Feb 17, 2015

Comments

coca cola on February 24, 2015

I have two questions about network setup:

1. admin/config/services/openid-connect-sso
should be the OpenID Connect Single Sign-on checked also on server page?

2. /sso.php
should be server included in $network array in file, or there should be only client sites?

patrick.dawkins@commerceguys.com Patrick Dawkins on March 3, 2015

Hi

1. Yes, the server should also have SSO enabled.

2. Yes (at the moment at least) the server should also be included. This ensures that the logout cookie is set for the server when the user logs out of a client site. I may be able to make that slightly more efficient in future versions.

coca cola on March 30, 2015

Another questions, this time about synchronization of custom fields:
1. How that should be done properly? Are those modules allow for sync. of profile fields by implementing some custom hooks and/or extending current plugin openid_connect_client?
2. What if field content will be changed by admin on oauth2 server and admin want to update this field instantly on all sites in openid network? How can he do that according to oath2? Sync. is called only on login, right?

patrick.dawkins@commerceguys.com Patrick Dawkins on May 21, 2015

Sorry I didn't see your comment for a while.

1. See the respective api.php files for hook documentation:
http://cgit.drupalcode.org/oauth2_server/tree/oauth2_server.api.php
http://cgit.drupalcode.org/openid_connect/tree/openid_connect.api.php

2. The server does not have any direct control over the client sites, so you would have to find another solution for this - yes, at the moment sync is only called on login. There is a hook, hook_openid_connect_post_authorize(), which lets you act after the user has logged in, and it gives you access to the OAuth 2 tokens for that user - I guess you could then store them for later use, with care.

toddleish on November 19, 2015

I have setup OAuth2 + OpenID SSO on a drupal server and OpenID SSO on the drupal client. Both are using self-signed SSL certificates. I have placeded the edited sso.php files in the root of each drupal instance. When I click the "Log in with Generic" button on the client site, I get this error:
Not Found
The requested URL /oauth2/authorize was not found on this server.
I am new to OAuth/OpenID, so I'm not sure how to troubleshoot or where to ask for support. I have read your tutorial, all the install docs and I've watched Nik Vahalik's video tutorial several times.

Any guidance will be appreciated!

Atif on January 29, 2016

After redirecting from client side (eg: ssoclient.dev), at server (eg: ssoserver.dev), an exception is thrown,please refer below.

Error
×
Warning: openssl_sign(): supplied key param cannot be coerced into a private key in OAuth2\Encryption\Jwt->generateRSASignature() (line 124 of /var/www/html/airliquide/sites/all/libraries/oauth2-server-php/src/OAuth2/Encryption/Jwt.php).
Exception: Unable to sign data. in OAuth2\Encryption\Jwt->generateRSASignature() (line 125 of /var/www/html/airliquide/sites/all/libraries/oauth2-server-php/src/OAuth2/Encryption/Jwt.php).

My guess, maybe oauth is not able to generate public and private key for client validation.

Is there something, I am missing?

yogeshchaugule8 on January 3, 2017

I know the thread is little old, but for openssl_sign() error (below):

Warning: openssl_sign(): supplied key param cannot be coerced into a private key in OAuth2\Encryption\Jwt->generateRSASignature() (line 124 of /var/www/html/airliquide/sites/all/libraries/oauth2-server-php/src/OAuth2/Encryption/Jwt.php).
Exception: Unable to sign data. in OAuth2\Encryption\Jwt->generateRSASignature() (line 125 of /var/www/html/airliquide/sites/all/libraries/oauth2-server-php/src/OAuth2/Encryption/Jwt.php).

The above error is normally caused by mismatch of key generated by "oauth2_server_generate_keys()" function in oauth2_server.module. To fix this we've to make sure that the "oauth2_server.openssl.cnf" is properly accessible to module, if on windows OS also confirm the path of file is proper. After this call "variable_del()" in "oauth2_server_get_keys()" to make sure the key is generated again. Check sample code below:

<?php
function oauth2_server_get_keys() {
 
variable_del('oauth2_server_keys');
 
$keys = variable_get('oauth2_server_keys', FALSE);
  if (!
$keys) {
   
$keys = oauth2_server_generate_keys();
   
variable_set('oauth2_server_keys', $keys);
  }

  return
$keys;
}
?>