Example: Web Server App Authenticating with NHS Digital OAuth
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.
This example as the following key features:
- OAuth Client credentials flow
- .NET Core website authenticating with an NHS OAuth server followed by calling NHS APIs
- Publication of public key within a JWKS
- Secure key storage with Azure Key Vault
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).
- 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
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.
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:
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.
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
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:
Follow this guide to setup your application:
Application-restricted RESTful APIs - signed JWT authentication - NHS Digital
We have detected that you are using Internet Explorer to visit this website. Internet Explorer is now being phased out…
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
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.
Main Links & Further Reading
- The main segments of code, that use the IdentityModel, were based on the code in Scott Brady’s blog: https://www.scottbrady91.com/OAuth/Removing-Shared-Secrets-for-OAuth-Client-Authentication
- RFC 7523 for JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
Other pages in this blog: