Example: Web Client & Server App Authenticating with NHS Digital OAuth
Some OAuth providers enable you to reuse the authentication of an OIDC provider. If you trust that OIDC provider, have only simply requirements, then it can save your application from managing it’s own user database. The NHS Digital API Platform has a similar option, and this example walks through how to integrate with it using the standard Microsoft ASP.NET Core libraries.
This solution has a number of features (surprisingly complex)
- Authentication is using NHS Identity
- The authorisation is a binary allow access / deny
- Authorisation is to both web pages on the test web app AND APIs from NHS Digital
- Uses standard Microsoft libraries
- We are NOT configuring a login page in the .NET project
Once the access token has been issued by NHS Digital:
- Cookie based authentication between client and web app server
- OAuth between web app server (Relying Party) and NHS Digital APIs
- Access & Refresh tokens stay on the server
- API Calls to NHS Digital must take place from your server, not client side
- Client side code for this to work? None
The OAuth model in use is:
- Authorisation code flow
- Three legged model
- Authentication is “hidden” behind authorisation call
The solution consists of:
- ASP.NET Core 3
- Single server page — implemented as a Razor Page for speed
- Uses the in-built Microsoft Authentication libraries
How does this work in a project?
This is a very compact example, all the hard work is triggered on the loading of the “protected page”:
- Startup — OAuth middleware services are configured to connect to NHS Digital API Platform
- ProtectedPage.cshtml — Authorize attribute is used to protect the whole page and trigger an authorisation event
- OnGet() Action — if successfully authorised, then prior to returning the page, a call to an example API is made with teh NHS Digital API Access Token returned during authorisation
Building the solution — the nitty gritty
Using Visual Studio 2019, first create the solution:
- ASP.NET Core Web App
- Web Application — 3.1, select No Authentication and Configure for HTTPS
Configuration: Add Services
The easy part is to simply add the Microsoft Authentication and Authorization middleware into the app request processing pipeline.
Add into Configure(IApplicationBuilder app, IWebHostEnvironment env):
Although this example is not using an identity from the Microsoft framework - UseAuthentication must be included. The really important aspect that it enable is allowing the middleware to stand up the “callback” end point on the server for the OAuth flow:
- Without this you cannot use the “authorization code” flow
- This is the “back channel” between the authorisation server and your web app (i.e. not the client browser)
- NHS ID authentication process to (the “authorize” leg) to hand back control to your web app (the Relying Party), via the client browser
Configuration: ConfigureServices method
To get the most out of this section, it is critical you open the sample Startup.cs file in another window: https://github.com/fromorbonia/OAuth-NHSD-API/blob/master/src/oauth-nhsd-api/Startup.cs
Configuring services code sets three services:
- Configure how the cookies behave
- Configure OAuth
If there is no cookie in the session, then tell the middleware how to challenge the user for authentication. In this case a custom scheme of the name NHSD is to be used.
Potentially there are number of options that can be set with your cookie, and by default .NET sets the ones to secure your cookie. Personally, I prefer explicitly setting security related items in the code so it is easy to validate, and clear to other coders (and more importantly new developers) just how it is secured.
Given the security of your application is at risk, you do need to fully understand the options:
Finally the relationship to the OAuth provider can be defined, and there is a lot to unpack here.
The configuration parameters are as follows:
The Startup configuration simply prepares the server and middleware, but no calls have been made to authenticate / authorise. When the user navigates to the “protectedpage”, the following occurs:
- Authorisation middleware checks for a valid cookie
- If it doesn’t exist or expired, the middleware will trigger the OAuth Auth Code flow
- This re-directs the client browser to the NHS Digital API authorize end point
- Another client side redirect to NHS Identity
- Authentication pages appear, and if successful, control is handed back to your middleware “callaback” endpoint
- Your web app now calls the /token endpoint and is issued tokens
Creating the ProtectedPage
In this example we are using a plain Razor Page, but we are carrying out server side processing you do want to create a PageModel
To get the most out of this section, it is critical you open the sample file in another window: https://github.com/fromorbonia/OAuth-NHSD-API/blob/master/src/oauth-nhsd-api/Pages/ProtectedPage.cshtml.cs
Calling out the using statements that are relevant:
There are then only two critical areas of the code:
- Telling .NET to protect the page
- Calling the API with the newly issued tokens
Telling .NET to protect the page
The [Authorize] attribute is placed on the class to trigger the Microsoft authorisation framework. With the startup config provided, it uses the “NHSD” OAuth config to carry out an authorisation & authentication
Calling the API with the newly issued tokens
If the authorisation completes, then the OnGet Action gets called. You would want to encapsulate this code, but in the example I have just placed it in the page.
The first part of the method retrieves the current user (set as part of the [Authorize] processing), and uses GetTokenAsync() method to retrieve the issued Access Token. The demo also gets the Refresh Token and Token expiry time — but you don’t have to.
The second part uses HttpRequestMessage to make the actual API call:
- Often the API root path for OAuth and the APIs will happen to be the same
- The access token is placed in a Bearer type Authorization header
- Call is made
Remaining code — used for the demo
There are three public properties (ResResponse, ResContent and SessionExpires) added to pass the values of the responses to the page being rendered.
A private variable (_configuration) is used to expose an injected Configuration service to access application settings and secrets
There is nothing at all added to ProtectedPage.cshtml that impacts the functionality, the HTML is just to display the results of the call
Configuring NHS Digital API Management Portal
For this to work, you need to have an application configured in the NHS Digital Developer Account. You need this to provide a Client ID and Secret for you to call the service.
You can follow the tutorial here to create the app:
User-restricted REST API tutorial - NHS Digital
Hands-on tutorial to help you learn how to connect to our user-restricted REST APIs.
The only extra instructions are that when you are configuring your App:
- Also activate the “Personal Demographics Service (Integration Testing)” API
- The callback URL has to be the one you defined: https://<Your web server address/MyWebServerCallBack, and (in this context) having a local host call back works
Put the Client ID & Secret into your secrets store and run the web app…
Detailed Security Considerations
This section isn’t a full analysis, really just looking at one aspect of this solution. Aspects that bothered me with this default Microsoft solution were (a) how strong is using cookie authentication, alongside (b) where does the session state get stored…
(a) —on my todo list to carry out some research to make further comment, it is in widespread use.
(b) — that is much more interesting, and frustratingly hard to find out just basic details. After a bit of digging it appears that the sensitive server side Access and Refresh tokens are encrypted and stored in the ASP.NET session cookie…
Part of the point in having the three legged auth is that the /token endpoint is called from a relying party that is also a “confidential client”. This is to ensure the tokens stay on the confidential client and are not distributed to a “public client”…
Storing the session state on each client, rather than on your web server infrastructure is brilliant for stateless server side applications that support a significant number of concurrent users. However:
- How sensitive are you to having your access tokens encrypted on a public client? With good encryption the end user won’t be able to decrypt it
- Size of the cookie (and associated header) could grow quickly in size causing multiple issues
If this is an issue, and you don’t want to switch to using a OAuth between your client and your server (introducing a second OAuth segment), you can consider using a Ticket Store in the Cookie configuration. This moves the session state server side, but you then have the associated state problems when running across a load-balanced solution.