Example: Web Server App Authenticating with NHS Digital OAuth

Aubyn Crawford
9 min readFeb 3, 2021

Server to server authentication has the simplest flow, using the client credentials flow. However, this reduces the security of the connection, so it is important to add extra levels to ensure the identity of the calling server. Private Key JWT client authentication adds extra security and is a more modern, and more flexible, alternative to TLS Mutual Authentication.

Server to server with JWKS

This example as the following key features:

Detailed workings

This solution has a number of features, but if you don’t understand OAuth Client Authentication with private key JWT — you must read up (see the bottom of this article for the details).

Main aspects:

  • Authentication & authorisation is carried out by an NHS OAuth server
  • The authorisation is a binary access or deny
  • Uses standard Microsoft libraries
  • Uses Azure Key Vault to securely store a single pfx file
  • A single pfx file is used to create the JWKS and sign the authentication JWT

Once the access token has been issued by NHS Digital:

  • Access token must stay on the server
  • No Refresh tokens are issued in client credentials flow
  • This example makes a call to a randomly selected NHS API
  • Client side code for this to work? None

Building the solution — the nitty gritty

The sample was created in Visual Studio 2019, key aspects:

  • ASP.NET Core Web App
  • Web Application — 3.1, select No Authentication and Configure for HTTPS
  • NuGet packages: “System.IdentityModel.Tokens.Jwt” — Microsoft standard JWT libraries; “IdentityModel” — helper library for OAuth and OIDC;
  • NuGet packages for Azure: “Azure.Identity”; “Azure.Extensions.AspNetCore.Configuration.Secrets”

As with all the examples I have done, there are better ways to structure projects and files — however, the focus here is on providing concise examples grouping the related code together in specific files.

How does this work in a project?

This is a very compact example, all the hard work is triggered on the loading of the “System to System Call” page. Infact, if you don’t want to publish your JWKS endpoint as part of this application, nor use Azure — there are only two files with relevant code in:

  • SysToSysCall.cshtml.cs — requests an Access Token and calls an API
  • PrivateKeyJWTHandler.cs — classes to encapsulate creating the JWT

To manage the Azure Key Vault you also need to update:

  • Program.cs — set up the Azure Identity and points to your Key Vault

If publishing the JWKS is needed, then this is done with some middleware:

  • Startup.cs — middleware service added to intercept and serve a JWKS file
  • PrivateKeyJWTHandler.cs — contains the middleware code

SysToSysCall.cshtml.cs

Either open the Razor page called “SysToSysCall” from the project, or open (in another window) the GitHub SysToSysCall.cshtml.cs file.

The file itself has comments in around each code block, fairly easy to follow. However, to better understand what is going on, the important objects / statements are:

ClientCredentialsTokenRequest class is from the IdentityModel package and is used to manage all the properties to make the Client Credentials request. Main items to configure:

  • Token endpoint details — as there is only one endpoint for this flow, simply set this value. There is no point using the OIDC well-known configuration to lookup the token endpoint
  • Client Id
  • Client Assertion

IdentityModel also includes the class ClientAssertion to manage the process of configuring this (including the setting of private key).

Once everything is configured then the HttpClient extension method RequestClientCredentialsTokenAsync() to carry out authentication and authorisation.

PrivateKeyJWTHandler.cs

This class would normally split across a number of files, but this demo is about showing how only a few extra classes can deliver a fully functional OAuth client so it has been left as a single file.

Either open the file from the project, or open (in another window) the GitHub PrivateKeyJWTHandler.cs file in another window.

If you don’t need to publish a JWKS endpoint, then you only need to consider the PrivateKeyJWTHandler class. This is made up of two methods:

  • ClientAuthJwtCreate() is the key method, and once more makes use of the IdentityModel package to encapsulate the management of this process.
  • CertifcateForPrivatePublicKeyPair() is a support method to retrieve a certificate which contains the private public key pair

Inside ClientAuthJwtCreate() it creates and signs a JWT for the client authentication, through the use of CreateJwtSecurityToken() method.

When creating the JWT, the following are the mandatory claims needed under the standards:

Table of mandatory claims

Program.cs

The changes in this file are only to add Azure Vault support using ConfigureAppConfiguration() in CreateHostBuilder(). This picks up on the in-built security of an Azure Web App that recognises the context and is provisioned with the relevant Azure Credentials.

See Program.cs on GitHub

Publishing a JWKS Endpoint with Middleware

The elegance of the JWT and JWKS interaction (particularly the reduced management and seamless key rollover benefits) can get lost with all the focus of setting up a service.

In this example, hopefully, you can see that once you’ve created your key pair — it is trivially easy to sign your Client Authentication JWT and to publish the related Public Key in a JWKS.

The only hidden aspect, in this example, is the “kid” value. This is the unique key that ties the private key to the correct public key in the JWKS — see the “Magic of JWKS” below. It is hidden because IdentityModel uses the thumbprint of the public key as the kid.

Startup.cs

The JWKS endpoint middleware is configured and loaded here.

Either open the file from the project, or open (in another window) the GitHub Startup.cs file in another window.

The middleware addition happens in just two lines of code:

  • JWKSPublicEndpoint middleware class is added in ConfigureServices()
  • Then in Configure() it calls the UseJWKSPublicEndpointMiddleware() extension

PrivateKeyJWTHandler.cs

Either open the file from the project, or open (in another window) the GitHub PrivateKeyJWTHandler.cs file in another window.

The middleware region in the handler file has one key class JWKSPublicEndpoint. It’s InvokeAsync() method looks at the incoming request and intercepts if the path is “/.well-known/jwks.json” and uses the JWKSGenerate() method to create a string with the JWKS in. This method uses the standard Microsoft JsonWebKeyConverter class to do this. This is a bit “dirty” for the following reasons:

  • Microsoft includes fields that aren’t mandatory
  • Some JWKS processing will be case sensitive, and the code has to force lowercase field names in the JSON. Which is made worse because there isn’t an included JsonNamingPolicy to do this
  • Just uses the JsonSerializer with minimum options

The code could be changed to have a strictly controlled JWK format, but if this is not done carefully — it could tightly couple the working version to a specific Microsoft library version.

Configuring NHS Digital to work in this example

For this to work, you need to have an application configured in the NHS Digital Developer Account. The example uses the Personal Demographics Service API, URL:

https://int.api.service.nhs.uk/personal-demographics/FHIR/R4/Patient/9436299680

Follow this guide to setup your application:

Creating Public and Private Keys

Thinking more about it, I’m not 100% sure PFX file formats were the best plan… However, if you don’t load the keys from Azure Vault and want to load the files up then the X509Certificate2 class works with PFX — so I’ll leave this example in.

The only output file you need is the PFX file, but to get there are two other files created:

  • .key — private key not used by .NET, but used to generate the other two.
  • .cer — holds the public key (text file)
  • .pfx — holds both public & private key (binary file)

I used openssl at the command line — which is fine, but it does insist on a config file. Making sure the config file is in the same directory, these three commands create an RS512 key pair (needed for NHS Digital):

The openssl.cfg file could look like:

Configuring Azure for this example

There is nothing specific about the Azure deployment to keep in mind, except that you need to create an Azure Key Vault as well. Once the App is created the following are the keys and values stored in the App Service Configuration:

  • “APIGetUri” — uri to the test API endpoint once authenticated
  • “OIDC:TokenEndpoint” — uri for the token endpoint
  • “VaultUri” — Azure Valut uri

The Key Vault needs the following:

  • In Secrets add “OIDCLocal \-\-ClientId”
  • In Certificates import the PFX file and name it “JWT \-\-PubPrivateKeyBase64”

The Magic of OAuth and JWKS

The power behind driving OAuth client authentication using signing and a JWKS (JSON Web Key Store) is simple, but enables so much. Combing the two results in the following benefits:

  • Modern alternative to TLS Mutual Authentication
  • The secret key is never exchanged between parties
  • The location of the JWKS acts an additional pre-shared secret
  • Seamless key rotation, that is self managed

There are plenty of sites that help you understand the intricate details, but the minimum you need to understand about client authentication:

  • Each time you request an Access Token, you create a JWT that contains key information about your server — but nothing sensitive (e.g. no client secret)
  • The private key is used to sign your JWT
  • The OAuth server, on receiving the JWT will validate it with a public key
  • It gets the public key from you, by you publishing a JWKS endpoint
  • A JWKS stores one or more public keys
  • Successful validation of the JWT cryptographically proves your server is legitimate

The “kid” and how it enables seamless key rotation

With all the above in place the last part to describe is how does the Auth Server know which public key to validate your signature with? As an owner of the Auth Server, you don’t want to accept the public key with each client authentication request as the caller could simply switch in their own public/ private key pair and you’ve not proved they are a legitimate authorised client.

Apart from initial configuration, the application developer never wants to talk to a help desk — and publishers of an API don’t want to have staff managing this type of configuration if they don’t have to.

The “key unique identifier” (or “kid”) is used to tie everything together:

  • Application developer app creates the client authentication JWT, choosing one of the available private keys. This key is identified by a “kid”
  • The “kid” is transmitted, in the JWT header, to the Auth Server
  • The Auth Server then retrieves the JWKS, based on a URL it has been configured with
  • The JWKS is opened, and the “kid” is used to select the matching public key
Showing how an application holds both the private and public key pairs, each with a unique “kid”

This mechanism theoretically allows the use of a different public / private key pair on each authentication. All the extra effort here means you don’t have to rotate keys very often, and many Auth Servers cache the JWKS for a long period (days / weeks) — although I am sure more and more are moving to much shorter intervals to reduce risks and make it much more self-serve.

Scaling out this example

Hopefully, the line count of this example shows that it is not that much effort to implement, despite the huge amount going on. To make this example full production ready there are a few aspects to consider.

JWKS In Production

If you used the JWKS middleware in the solution, it is great for a small single server or active/ passive style solution. This isn’t very scaleable, but the JWKS is a static file and the most scalable options is to automatically push your JWKS to a CDN (which can handle massive load).

Rotating your public / private key pairs is also essential, and ideally you want your CI/CD pipeline to:

  • Regularly generate new pairs, with unique kid’s
  • Publish a new JWKS with all current public keys (with their kid’s), and at least the last set of public keys if you want to stagger changing the config of your servers
  • On config update or deployment (if using immutable infrastructure) tell the client authentication code which “kid” to use

With the example it uses one certificate file to drive both paths, so automating the above should be straight forward.

Magic strings in the code

In a number of places magic strings are used to ensure the example works with NHS Digital defaults. These should be abstracted as extra parameters for the relevant methods.

--

--

Aubyn Crawford
0 Followers

I have worked as an independent IT Consultant for 26 years. I excel at bringing together and architecting complex solutions, and delivering them.