Example: Single Page App using Microsoft OIDC

Aubyn Crawford
7 min readDec 28, 2020

For very simple apps, especially where you don’t need an active server, then a Single Page App is a good solution. OIDC allows you to secure your application with someone else’s authentication and identity information (such as Facebook). You don’t have anything heavy weight to implement at your end!

Previously, the Implicit Grant flow was very quick — but it is no longer recommended for a number of reasons. The main reason, is that the Access Token was passed as a query parameter, and that can be leaked in the browser history or logged in any infrastructure that terminates the TLS connections.

You should use Authorisation Code flow with PKCE. This flow is designed to eliminate a number of attack scenarios. The use of PKCE removes the main risks around having an auth code stolen and using it on another device. PKCE also aligns nicely with the OAuth Native App approach.

The solution consists of:

  • ASP.NET Core 3
  • Three server pages — implemented as Razor Pages for speed
  • OpenID Certified JavaScript library: oidc-client-js

This example is about integrating to Microsoft Azure AD (AAD), and AAD is used to provide the following:

  • User database
  • OIDC based authentication
  • A registered test application (no actual functionality)
  • A random Microsoft API, to demonstrate the client making a call to a secured API using the retrieved access token

How does this project work?

Context is always important and it this diagram summarises what the security segments of the solution are:

With the help of a good JavaScript library (oidc-client-js in this case), the configuration can be setup in just one place — in this example it is in Index.cshtml.

However, the Auth Code flow requires a callback location. So, at a minimum, you have to have two pages:

  • Index.cshtml — this is your Single Page App and will have three buttons: Login, Call API and Logout
  • OIDCAuthCodeCallback.cshtml —registered endpoint that the OIDC provider needs to exist to complete the login sequence. This is a static page, and all the processing is client side to retrieve the Access & Refresh Tokens

The JavaScript script library does all the hard work — using a mixture of local and session storage in your browser to persist security information, it can implement the full flow.

I mentioned three pages… So the third page isn’t technically essential. Any solution where you have short lived access tokens (which should now be most apps!) you want the client application to regularly check if the user is still allowed to be logged in. There are two ways that the user can be issued a new access token without logging in:

  • The previously issued refresh token is used
  • The OIDC provider recognises that you are logged in (normally using cookies your browser)

OIDC provides an invisible iFrame to check if the session is still valid and issue a new access token if that session is still valid. To use this mechanism you have to create this page:

  • SinglePageAppSilent.cshtml — this has no layout, simply instantiates the OIDC client JS to process the silent call back

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
  • By default this contains jQuery and Bootstrap — jQuery is required for oidc-client-js

Now the oidc-client-js needs to be added in, I just use the ASP.NET default libman.json. There are quite a few JavaScript OIDC clients out there, so really easy to select the wrong one. For clarity, the entries in libman.json are:

Index.cshtml

To get the most out of this section, it is critical you open the sample Index.cshtml file in another window.

Running through the code on GitHub — at the top of the page, there is an included a reference to the OIDC JS library. This should really go in the _Layout.cshtml, but I’ve put it at the top of the page to encapsulate the logic.

Then there is a section of DIVs using Bootstrap to aid readability and layout.

Now the interesting parts of the page. A scripts section contains a jQuery document ready function. Inside of which are the following code sections:

  • Config
  • Initialise OIDC User Manager
  • Respond to Login & Logout
  • Display logged in status & user information
  • Call the fake API

Config section

This is a very short section, and demo’s the power of OIDC as well as a good client library. Your Microsoft Azure AD tenant publishes all the config at the standard endpoint so you don’t need to put it in your app. The endpoint is at:

https://login.microsoftonline.com/<Your TenantID>/v2.0/.well-known/openid-configuration

By providing the path up to the “/.well-known…” the client library automatically tries that full URL and configures itself for your tenant.

A full breakdown of the config parameters:

Initialise OIDC User Manager & Login

Now that you have setup the config, you initialise a variable to hold and manage the OIDC connection.

The code to respond to the Login & Logout buttons rely on straightforward methods in the library, no extra explanation needed.

Login sequence

The OIDC login sequence is very complex and the signinRedirect() method carries out the following main actions:

  • If there is no metadata defined, it calls the .well-known/openid-configuration endpoint and configures itself (include setting up the PKCE values)
  • Uses LocalStorage to hold the temporary values needed
  • Calls the Authorize endpoint, and hands the browser over to the OIDC provider
  • OIDC provider then manages authentication
  • Returns control back to your application, via the page specified in the redirect_uri parameter

Display logged in status & user information

The getUser() method returns a promise, which the code then processes to work out if any user is logged in or not. This displays key session information for the purposes of this demo.

So in the code example, the “user” object is empty until after a successful authentication:

  • getUser() looks in the Session Storage for the object created on success
  • user.profile is automatically populated by a call to the userinfo endpoint, the contents of which is normally determined by the scopes you originally sent over

Call the fake API

Again, using the getUser() method, retrieve the Access Token and place it in the Authorization header and call the API.

In this case, I picked a random Microsoft Graph API that I can protect with Azure. If the authorisation worked then you will get a 404 with error details (unless you have a valid Sharepoint license).

If you haven’t logged in, or the token is invalid - then it will return 401 unauthorized.

SinglePageAppCallback.cshtml

Add a new Razor Page to the project - as this a client side app only, do not include the generation of the PageModel class.

To get the most out of this section, it is critical you open the sample SinglePageAppCallback.cshtml file in another window.

As with the Index page a script reference is needed.

If all goes well, the user will never see this page — but if there is an error I included a paragraph tag with an ID to display a message.

Scripts section

Contains three elements:

  • Completing the OIDC flow
  • Sending the user back to the main page — this will vary depending on your actual use case
  • Error function to display error info

Completing the OIDC flow

This is the second part of the OIDC auth flow, and covers:

  • Validates the response from the OIDC provider
  • PKCE validation occurs to make sure someone else has not hijacked the authorisation code
  • If all the checks pass, then call the /token endpoint to issue the Access and Refresh tokens
  • Storing the results in Session Storage rather than Local

SinglePageAppSilent.cshtml

Add a new Razor Page to the project — as we are making this a client side app only, do not include the generation of the PageModel class. There should be no layout for this either, it is just a minimal page to call out and do the session check.

To get the most out of this section, it is critical you open the sample SinglePageAppSilent.cshtml file in another window.

As with the Index page a script reference is needed.

If all goes well, the user will never see this page.

Scripts section

Contains three elements:

  • Initialising the JavaScript user manager
  • Configuring the user manager to send the silent sign-in results to the parent window
  • Error function to display error info

Configuring Microsoft Azure

This example is using the new (in 2020) support by Azure Active Directory for single page apps.

  • Login to https://portal.azure.com
  • Use the left hand menu to navigate to Azure Active Directory. You should have a default tenant selected.
  • Register a new application, and select an SPA type redirect URI:

That is all that is required of the App config on MS! All that you need to do now is place the Client ID and Tenant ID into your project config (secrets.json for local projects). These values are on the App Overview page:

Main Links & Further Reading

Other pages in this blog:

--

--

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.