Skip to main content

Mydex CIC - Tech Blog

How Mydex uses OpenIDConnect and OAuth (Part One)

Table of Contents

Mydex CIC describes their Identity as a Service platform, its evolution and adoption of protocols such as OpenID Connect and OAuth2.0, and how OIDC principles align with the ethos of the Person at the centre of data exchanges.

# Introduction

The Mydex Safe, Secure Cloud is a broad ecosystem that has, at its heart, the ‘person’ (a.k.a the member) and their personal data services (PDS and PDX).

The intent of the platform is to keep the member fixed at the centre of a data ecosystem that too often sees third parties syphon off data for their own purposes, or force the member to jump through annoying, repetitive hoops of friction and effort to supply data that the person has already offered up before.

In the early days of Mydex, we had a single application that provided both registration and login so that the members could create and manage their PDS. This model began to break down once we realised we needed / wanted to build other applications that interact with the member’s PDS.

We also realised that one step towards reducing friction and effort for the member when using applications on the internet, was to reduce how many credentials they needed to use those services. Credential drift is both a security issue and another example of the member not being at the centre of the exchange of their personal information with suppliers or vendors.

These days, lots of people use Google or Facebook or similar to authenticate to applications. This is the convenience that a Single-Sign-On (SSO) identity service provides. However, they may not give much thought (or feel that they have a choice) about the fact that these vendors are part of the surveillance capitalism ecosystem, designed to operate as a dragnet of their personal data that mines their information to sell to others. These models fly in the face of what Mydex is all about: putting the member back in control.

To support being able to sign into services that support the Mydex Personal Data Store and its ethos, Mydex launched the Identity as a Service (IDaaS) platform.

Part One (this post) will talk about the evolution of the IDaaS platform.

Part Two will follow soon and will talk about how we are innovating further with IDaaS and what comes next.

# What is the Mydex IDaaS platform?

When a member registers with the Mydex Safe, Secure Cloud, they get a Personal Data Store (PDS) that is theirs free for life. They also get what we refer to as a ‘MydexID’, which is their unique identifier on the platform.

Many Mydex-driven applications, as well as those by third party subscribers that are participating in our program, support MydexID as an Identity Provider through standard, open protocols such as OpenID Connect.

When a member signs in with their MydexID, a record of this is stored in their own PDS. They can treat this as an audit log that shows when their MydexID was authenticated and to what application the authentication was being performed. The member can review this log at any time by visiting their PDS.

When a member has already authenticated with their MydexID to one application, and then visits another that also supports MydexID, they do not need to re-authenticate with their credentials. This is what’s known as ‘Single Sign On’.

Keeping track of which applications you’ve logged into could become complicated. So when the member logs out of one application, all other applications that support a logout flow with the protocol also log the member out of those applications, for security purposes.

# The underpinnings of the IDaaS platform, then and now

Today, Mydex leverages the powerful OpenIDConnect standard as part of its Identity platform. OpenIDConnect is an extension of the older OAuth2.0 protocol. Its intention was to deliver for real people the benefits of OAuth2.0, the latter of which is mostly about machine-to-machine authentication.

When we launched the IDaaS platform many years ago (2013), OpenIDConnect did not yet exist - it was introduced in early 2014, and there were few software libraries that leveraged it for some time yet. There was the older OpenID 2.0 protocol, but it was cumbersome to use (and is now considered completely obsolete).

At the time, Security Assertion Markup Language (SAML) was a much older, but therefore stable standard that is still quite popular for performing single sign-on solutions. Mydex leaned heavily on SAML over many years for the Identity Platform, but over the years, SAML has also started to show its age and can be cumbersome to implement.

Mydex also were enthusiastic early adopters of the Mozilla Persona protocol, a short-lived idea for decentralised authentication on the internet. This protocol was also complex to set up, had some significant design issues, and ultimately did not achieve much traction. It was eventually decommissioned entirely by Mozilla.

As the world has also shifted more and more to the use of mobile applications for interacting with the internet, there was a need for something more secure and more ’native’ to the way we use the internet today. OpenIDConnect has emerged as offering improvements over SAML.

Today, Mydex still offers SAML but it is considered a deprecated service that we intend to sunset towards the end of 2024, at the earliest.

# Searching for an open source OpenIDConnect solution that suits Mydex

Mydex runs a number of services that deliver the ISO27001-certified, highly available platform. At the foundations, those services are built on open source software.

Although what we do is often a form of pioneering of the ways in which citizens can manage their personal data in their interactions with third parties, we try not to build anything from scratch, instead leveraging mature standards, protocols, programming frameworks and off-the-shelf solutions.

Mydex is also unique in that it needs to operate as the Identity Provider for its members (in OpenIDConnect, this is called the OpenID Provider or OP), as well as the Relying Party (RP) as part of web applications built by Mydex for our own platform services, which are used by both our Members and service provider Subscribers.

Finding software to operate as an RP is easier, as many programming languages have popular libraries that implement the OpenIDConnect protocol. In Mydex’s case, we use JumboJett’s OpenIDConnect library for PHP, which has been great. We even contributed back support for much sought-after OpenIDConnect protocol features, such as Back-Channel Logout.

However, what is harder is finding software that operates as the OP - particularly if you already have an established identity data system (for example, a database of login credentials).

For a number of years, we searched for ready-made OpenIDConnect solutions for running an OP that could be integrated easily into an existing database. What we found was that most off-the-shelf solutions assumed you were building from the ground up, and wanted to force you to store all the credentials in their own database.

Often this also entailed harnessing software we didn’t really want to have to add into our estate, such as LDAP. Gluu was an example of this. We also didn’t want to completely outsource our identity database to a third party such as Okta.

## Discovering Ory Hydra

Fortunately, we eventually identified a new product that understood our use case. That product was called Hydra, made by the team at Ory.

Hydra is different from other OP software in that it assumes you already have a database somewhere holding your member credentials, and some means of querying that database to validate credentials sent from a device (e.g a login form). The way it works is that it provides the OpenIDConnect flows that begin at the Relying Party side, but as soon as it needs to actually authenticate the member, it ‘hands off’ to an application you’ve built yourself for that purpose - often called the ‘Login and Consent’ application or LaCa.

The LaCa’s job is merely to let the member enter their credentials, and then tell Hydra that the authentication was a success. At that point, Hydra completes the rest of the standard OpenIDConnect flow and the Relying Party application can then use typical OIDC processes to ’learn’ information about the authenticated user (e.g check the value of ‘claims’ at the OIDC ‘UserInfo’ endpoint).

Hydra has been great for Mydex because it means that it ’taps in’ to our existing identity service in exactly the same way that SAML does. It makes no assumptions about what that database looks like. We did not need to lift-and-shift our data backend into an equivalent but often opaque or over-complicated vendor solution.

Even though we are sunsetting SAML in the near future, this meant we were also able to easily support both protocols without duplicating our backend.

## Interacting with the Hydra API

Hydra typically runs as a Docker container, and it’s also possible to invoke Hydra via Docker to execute one-off commands, such as creating or updating an OIDC or OAuth2.0 client.

At Mydex, we are in the process of building out a more comprehensive subscriber application that will allow subscribers to request OIDC or OAuth2.0 clients. On the other side of this, Mydex staff will have an internal application to make it easier to review and approve those requests. Part of that approval process will involve automating the creation of the Hydra OIDC/OAuth2.0 clients without having to resort to command-line tools or custom scripts.

Further to this, the release of Hydra 2 resulted in the loss of the ability to set a password for the OIDC/OAuth2.0 client. This was a problem for us because our model involves giving the Subscriber their credentials in advance of the approval of the client, so that they can get to work building their integration even before the credentials are ready to be used.

A later release of Hydra re-introduced the ability to set the password, but only via the API, not via the CLI tools. So, we ended up learning how to interact with the API directly.

Below is an example of how to create an OIDC client via PHP. To use the script, you need to run ‘composer install ory/hydra’.

Click to see the script
  1<?php
  2
  3require 'vendor/autoload.php';
  4
  5use \Ory\Hydra\Client\Api\OAuth2Api;
  6use \Ory\Hydra\Client\Configuration;
  7use \Ory\Hydra\Client\Model\OAuth2Client;
  8use \GuzzleHttp\Client;
  9
 10class HydraClient
 11{
 12
 13    private $adminApi;
 14
 15    public function __construct($adminUrl)
 16    {
 17        $config = Configuration::getDefaultConfiguration()->setHost($adminUrl);
 18        $this->adminApi = new OAuth2Api(new Client(), $config);
 19    }
 20
 21
 22    public function createClient(
 23	    $clientId,
 24	    $clientSecret,
 25	    $clientName,
 26	    $grantTypes,
 27	    $responseTypes,
 28	    $scope,
 29	    $redirectUris,
 30	    $postLogoutRedirectUris,
 31	    $backchannelLogoutUri,
 32	    $backchannelLogoutSessionRequired,
 33	    $subjectType,
 34	    $tokenEndpointAuthMethod,
 35	    $allowedCorsOrigins=null
 36    )
 37    {
 38        $client = new OAuth2Client();
 39        $client->setClientId($clientId);
 40        $client->setClientSecret($clientSecret);
 41        $client->setClientName($clientName);
 42        $client->setGrantTypes($grantTypes);
 43        $client->setResponseTypes($responseTypes);
 44        $client->setRedirectUris($redirectUris);
 45        $client->setScope($scope);
 46        $client->setPostLogoutRedirectUris($postLogoutRedirectUris);
 47        $client->setBackchannelLogoutUri($backchannelLogoutUri);
 48        $client->setBackchannelLogoutSessionRequired($backchannelLogoutSessionRequired);
 49        $client->setSubjectType($subjectType);
 50	$client->setTokenEndpointAuthMethod($tokenEndpointAuthMethod);
 51	if (!is_null($allowedCorsOrigins)) {
 52            $client->setAllowedCorsOrigins($allowedCorsOrigins);
 53        }
 54
 55        try {
 56            $createdClient = $this->adminApi->createOAuth2Client($client);
 57            return $createdClient;
 58        } catch (Exception $e) {
 59            syslog(LOG_DEBUG, $e->getMessage());
 60            return ['error' => $e->getMessage()];
 61        }
 62    }
 63}
 64
 65function prompt($message)
 66{
 67    echo $message . ": ";
 68    return trim(fgets(STDIN));
 69}
 70
 71function validateOrigin($url)
 72{
 73    $parsedUrl = parse_url($url);
 74    if (!isset($parsedUrl['scheme']) || !isset($parsedUrl['host']) || isset($parsedUrl['path']) || isset($parsedUrl['query']) || isset($parsedUrl['fragment'])) {
 75        return false;
 76    }
 77    return true;
 78}
 79
 80$adminUrl = "https://your-ory-hydra-admin-url.example.com"; ## Don't forget to change this!
 81$clientId = prompt("Enter client ID");
 82$clientSecret = prompt("Enter client secret");
 83$clientName = prompt("Enter client name");
 84$grantTypes = explode(',', prompt("Enter grant types (comma separated)"));
 85$responseTypes = explode(',', prompt("Enter response types (comma separated)"));
 86$scope = prompt("Enter scopes (space separated)");
 87$redirectUris = explode(',', prompt("Enter redirect URIs (comma separated)"));
 88$postLogoutRedirectUris = explode(',', prompt("Enter post logout redirect URIs (comma separated)"));
 89$backchannelLogoutSessionRequired = filter_var(prompt("Is backchannel logout session required (true/false)"), FILTER_VALIDATE_BOOLEAN);
 90if ($backchannelLogoutSessionRequired) {
 91    $backchannelLogoutUri = prompt("Enter backchannel logout URI");
 92} else {
 93    $backchannelLogoutUri = "";
 94}
 95$subjectType = "pairwise";
 96$pkce = filter_var(prompt("Is it a PKCE client? (true/false)"), FILTER_VALIDATE_BOOLEAN);
 97if ($pkce) {
 98    $tokenEndpointAuthMethod = "none";
 99} else {
100    $tokenEndpointAuthMethod = "client_secret_basic";
101}
102$allowedCorsOrigins = null;
103$cors = filter_var(prompt("Does it need extra CORS origins? true/false)"), FILTER_VALIDATE_BOOLEAN);
104if ($cors) {
105  $allowedCorsOrigins = explode(',', prompt("Enter URLs for CORS origins. Remember to include https://"));
106  foreach ($allowedCorsOrigins as $origin) {
107    if (!validateOrigin($origin)) {
108        die("Invalid allowed CORS origin specified: $origin");
109    }
110  }
111}
112
113$hydra = new HydraClient($adminUrl);
114$response = $hydra->createClient(
115    $clientId,
116    $clientSecret,
117    $clientName,
118    $grantTypes,
119    $responseTypes,
120    $scope,
121    $redirectUris,
122    $postLogoutRedirectUris,
123    $backchannelLogoutUri,
124    $backchannelLogoutSessionRequired,
125    $subjectType,
126    $tokenEndpointAuthMethod,
127    $allowedCorsOrigins,
128);
129
130echo json_encode($response, JSON_PRETTY_PRINT);

This was very useful because this code (or a close variant of it) will end up as part of our aforementioned internal application for driving the creation of the client upon approval.

## OpenIDConnect for applications that don’t support it: Vouch to the rescue!

We often use software that doesn’t have native support for OpenIDConnect, especially when it comes to internal operational tools. Sometimes we even build light applications or need to lock even a static site down from unauthenticated access.

To avoid having to build OpenIDConnect support for such applications, we make use of another open source solution called Vouch.

Vouch leverages the auth_request subrequest system of Nginx, which means that if you have an application using the Nginx web server, you can very easily put it ‘behind’ an OpenIDConnect auth wall, merely by adding some settings to your Nginx virtual host configuration.

The application itself does not need to understand OpenIDConnect at all. Nginx’s auth_request will intercept the request and hand off to Vouch to complete an OIDC flow on the app’s behalf. Vouch will return the member to the original app, along with (optionally) some headers containing the values of OIDC claims, which can be used by your application to establish a session or treat the member as logged in (if relevant). We’ll show an example of that further below.

### Vouch as an ACL and WAF

A residual benefit of Vouch is that it supports an allow-list parameter which can limit access to a list of users (based on a claim value). As a result, Vouch not only brings OpenIDConnect functionality to an application, but also acts as an ACL layer. We don’t necessarily want anyone with a MydexID being able to authenticate to certain applications intended for operational use.

The fact that Nginx’s auth_request intercepts a request before it has a chance to hit the backend application also makes it operate as a pseudo Web Application Firewall (WAF), blocking unauthenticated access to the application altogether, potentially mitigating a variety of attack vectors.

Mydex has also been proud to contribute features and fixes to the Vouch project over the years. One such example was the ability to make certain timeout values configurable at runtime, which is important if using a load balancer in front of the Vouch application, such as an AWS ALB, where idle connections get closed after a certain period. Read more about why this is important.

### Example: configuring Vouch for Jenkins

Below is an example of configuring an Nginx vhost, intended for a Jenkins backend server, to use Vouch and pass the mydexid as a header to the backend service.

Notice how the auth_request parameter is in the server block, meaning it intercepts all requests to the Jenkins service.

If the member is not logged in, a 401 is returned from Vouch, which then initiates the 302 redirect to Vouch to complete an OIDC flow.

We use the Reverse Proxy Auth plugin in Jenkins to tell it how to interpret the header X-Vouch-IDP-Claims-Username passed by Nginx (learned as an OIDC claim via Vouch) as being the Jenkins username, in order to automatically establish the Jenkins session.

Click to see the Nginx vhost
 1upstream jenkins {
 2  keepalive 65;
 3  server 127.0.0.1:8080 fail_timeout=0 max_fails=0;
 4}
 5
 6# Required for Jenkins websocket agents
 7map $http_upgrade $connection_upgrade {
 8  default upgrade;
 9  '' close;
10}
11
12server {
13  listen 443;
14  server_name jenkins.example.com;
15  ssl on;
16  ssl_certificate       	/etc/nginx/ssl/jenkins.example.com.crt;
17  ssl_certificate_key   	/etc/nginx/ssl/jenkins.example.com.key;
18
19  root /var/run/jenkins/war/;
20
21  auth_request /validate;
22  set $vouch vouch.example.com;
23  location = /validate {
24	resolver 8.8.8.8 8.8.4.4 ipv6=off;
25	proxy_ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
26	proxy_ssl_session_reuse on;
27	proxy_ssl_verify_depth 2;
28	proxy_ssl_verify on;
29	proxy_ssl_name $vouch;
30	proxy_ssl_server_name on;
31	proxy_pass_request_body off;
32	proxy_set_header Content-Length "";
33	proxy_set_header Host $vouch;
34   
35	auth_request_set $auth_resp_x_vouch_idp_claims_username $upstream_http_x_vouch_idp_claims_username;
36	auth_request_set $auth_resp_jwt $upstream_http_x_vouch_jwt;
37	auth_request_set $auth_resp_err $upstream_http_x_vouch_err;
38	auth_request_set $auth_resp_failcount $upstream_http_x_vouch_failcount;
39   
40	proxy_pass https://$vouch/validate;
41  }
42   
43  error_page 401 = @error401;
44   
45  location @error401 {
46	return 302 https://$vouch/login?url=$scheme://$http_host$request_uri&vouch-failcount=$auth_resp_failcount&X-Vouch-Token=$auth_resp_jwt&error=$auth_resp_err;
47  }
48
49  location / {
50	auth_request_set $auth_resp_x_vouch_idp_claims_username $upstream_http_x_vouch_idp_claims_username;
51	proxy_set_header X-Vouch-IDP-Claims-Username $auth_resp_x_vouch_idp_claims_username;
52	proxy_set_header Host $http_host;
53	proxy_set_header X-Real-IP $remote_addr;
54	proxy_set_header X-Forwarded-Proto https;
55	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
56	proxy_http_version 1.1;
57
58	# Required for Jenkins websocket agents
59	proxy_set_header   Connection    	$connection_upgrade;
60	proxy_set_header   Upgrade       	$http_upgrade;
61	proxy_max_temp_file_size 0;
62	proxy_connect_timeout  	90;
63	proxy_send_timeout     	90;
64	proxy_read_timeout     	90;
65	proxy_buffering        	off;
66	proxy_request_buffering	off;
67	proxy_set_header Connection ""; # Clear for keepalive
68	proxy_redirect http:// https://;
69	proxy_pass http://jenkins;
70  }

# OAuth2.0 and the Mydex Platform

Historically, Mydex has operated its Personal Data Exchange (PDX) API with a bespoke API key system. Whilst this simple solution served us well over many years, it has some notable drawbacks:

  • Anyone could register an API key without approval (even though, to do anything useful with it, a Dedicated Connection Key is also needed to be issued by Mydex)
  • The API key is long-lasting. If it is ever compromised, it needs to be completely replaced.
  • The API keys could not be scoped to specific use cases

Yet another major benefit of choosing Hydra as our OIDC product is that, given that OIDC is a protocol built on top of OAuth2.0, we also get an OAuth2.0 server ‘for free’. This has allowed us to shift our PDX API to make use of OAuth2.0 client credentials for authenticating requests.

This solves all the above problems:

  • OAuth2.0 client credentials are requested by subscribers, but issued by Mydex
  • OAuth2.0 client credentials are used only to obtain a short-lived access token (1 hour). It is the short-lived access token that is authorised by the API to access the relevant service. This means that if a short-lived access token is somehow exposed later, it has probably expired, and the original credentials, if not also exposed, do not need rotation.
  • OAuth2.0 client credentials can be scoped and this can be/is checked by our API middleware to ensure that the token is not only valid (active), but also authorised for the requested route.

There are plenty of other security benefits to OAuth2.0 over bespoke symmetric key authentication.

# To be continued

In Part Two of this blog series, we’ll talk a bit more about ways we’ve learned to innovate certain journeys by leveraging the paradigm of OpenIDConnect, and what we envisage for the future as the standard continues to evolve and be enriched with new ideas.