My Notes: Using Azure AD and MSAL for Application Authentication

When building an internal application you probably have authentication and authorization needs. If you already have a Single Sign On (SSO) provider like Auth0 or Okta then you are in pretty good hands as they both have great documentation and features to support these kinds of scenarios. For the rest of us, we need to get creative and use the tools we already have available. For some of us Azure AD may be that tool. If your business is already using Office 365 or Azure you may be able to use Azure AD to meet your internal authentication needs. To add convenience, you may even already have your desired users entered in Azure AD and organized into groups.

Because Azure AD uses OpenID Connect and JWT you can use whatever tools you want to work with it. I ended up using the MSAL libraries for JavaScript and ASP.NET Core. Azure AD has a lot of good documentation and there are little bits of knowledge scattered around their samples but I really felt that it was difficult to bring it all together into a working solution. Judging from the way the documentation was written and from the available samples it seems to me the use case they had in mind was for users wanting to build their own Graph Explorer. This post is just a collection of things that helped me bring it all together for my use case.

Creating an Application

Before you can use Azure AD for authentication you have to create an Azure AD Application in the poral. This Azure AD Application will control which of your applications can use it for authentication and what kinds of information will be provided about your user. To create an application, click on “App registrations” within the Azure AD portion of your Azure Portal. You can leave the redirect URL blank at first as you may not know what to use for it yet, you can change them later.

After the Azure AD application has been created, you need to get some values out of it to use when configuring your software:

  • Client ID - This can be found in the Overview section of your application in the portal.
  • Domain - This can be found in the Branding section of your application in the portal.

Note that you have a limited number of “SSO Applications” you can create on the O365 and Free tiers of Azure AD. I had a lot of trouble understanding the criteria for what makes an Azure AD Application count as an “SSO Application” but you may be able to use a single Azure AD Application to meet your needs.

Configuring an Application

Authentication

You will need to configure your redirect URLs that each application sends. If you have multiple services that will integrate with this application you can enter multiple redirect URLs for them. If you don’t know what these values are yet, you can enter them later.

When your Redirect URIs are not configured correctly, you may get an error message like this:

AADSTS50011: The reply URL specified in the request does not match the reply URLs configured for the application: ‘ffa9e143-853a-4b45-8e03-d2cf6a5abaf1’

This error means that the redirect_url that your service or app is using does not match what is configured in the Azure AD Redirect URIs list. You can find the actual used value in the HTTP request your browser sends to Microsoft by looking for the redirect_url parameter. Note that it can take a minute for new values to work.

If you are using Implicit Flow for authentication you may need to check the “ID tokens” checkbox under Implicit grant. Your needs may vary and the error messages you get are pretty good at telling you what you need.

Manifest

While a bit of an odd user experience, if you want to enable groups in your token claims you will need to edit the Manifest JSON. To allow groups you need to add or edit the "groupMembershipClaims" property as described in the documentation.

Token configuration

You can edit tokens/claims directly in the manifest, but I recommend using the provided tools on this screen when possible. Using the Token configuration screen you can add additional claims to your token if you think they may be useful. As an example you can add an ipaddr claim using the “Add optional claim” tool. More likely, you will want to add group claims to your token so you can more easily restrict different areas of your app to different groups. Use the “Add groups claim” button to add group claims to your ID token. In my case I found that “Group ID” worked best for me. Note that when your services and apps request a token they will be given these optional claims even if they don’t ask for them, so no need to explicitly request them using scopes.

Working with JavaScript

The MSAL libraries from Microsoft help a lot to integrate with Azure AD but there are some quirks to be aware of.

Installation

When working with JavaScript you only need to install msal. Working with Angular however is a bit more work as you may have to experiment with different versions of different packages before things work for you. I was able to get MSAL working with Angular 8 when I tried by using a beta version of the @azure/msal-angular at ^1.0.0-beta.1. Additionally there seems to be some confusion regarding if you should reference msal or @azure/msal, so I guess try them and see which one works for you 🤷.

Configuring

Configuring MSAL is mostly straightforward with the exception of a few quirks and deviations from the documentation that I ran into. You can navigate around the outdated documentation by using typescript or exploring the MSAL code yourself on GitHub.

The first odd bit that I had to work through was providing a working configuration for the protectedResourceMap property. Instead of accepting an object or array it must be an instance from new Map<string, string[]>() in Typescript or new Map() in JavaScript. You can either feed an array of arrays into the constructor or call the .set method directly. Either way though it makes configuring your application a bit painful and awkward as your environment.ts file (or whatever solution you use) may end up containing something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
export const environment = {
  // ...
  protectedResourceMap = <[string, string[]][]>[
    ['https://localhost:12345/', ['openid profile']]
  ]
  // ...
}

// ...

MsalModule.forRoot({
  // ...
  framework: {
    // ...
    protectedResourceMap: new Map<string, string[]>(environment.protectedResourceMap)
  }
})

In addition to the configuration troubles I had I was also surprised to find that if I wanted the SPA application to acquire an ID token I had to change the requested scopes to be the Client ID instead of the expected openid profile. I’m sure this has to be wrong but I found a sample that demonstrated this and also can see it in the code for UserAgentApplication.ts .

Working with ASP.NET Core

Microsoft provided some samples to work with ASP.NET Core and OIDC but they all seem to have a reference to a project that I didn’t want to copy around. The good news is that this extra project isn’t doing a whole lot and you can study it to learn what is needed to get OIDC working with a standard ASP.NET Core setup: WebAppServiceCollectionExtensions.cs

JWT Validation

Instead of having ASP.NET handle the full authentication flow, it can just simply validate tokens that a SPA application holds. Validating tokens should be as simple as any other JWT validation setup in ASP.NET. You should have no trouble validating the audience, signing key, issuer, and lifetime. The authority will be used to get the signing keys from the well-known document, should match your iss claim from the token, and should match the tenants you are using. The audience should be set to your Client ID defined in the Azure AD application. I think the correct NameClaimType to use is "preferred_username" based on the examples but I’m not positive. Here is an example to get started with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
.AddJwtBearer(AuthConfig.AzureADScheme, opt =>
{
  var tvp = opt.TokenValidationParameters;

  if (HostEnvironment.IsDevelopment())
  {
      opt.IncludeErrorDetails = true;
  }

  opt.TokenValidationParameters.NameClaimType = "preferred_username";

  opt.Authority = Configuration.GetValue<string>("AuthConfig:AzureAD:Authority");
  tvp.ValidateIssuer = true;
  tvp.ValidateIssuerSigningKey = true;
  tvp.ValidateLifetime = true;

  tvp.ValidAudience = Configuration.GetValue<string>("AuthConfig:AzureAD:Audience");
  tvp.ValidateAudience = true;
});