Craig Bruce

Multi Tenanted SaaS Applications using Azure Active Directory

I have recently been responsible for architecting and implementing a business-to-business SaaS application where the vast majority of end users are enterprise Office 365 subscribers, therefore it made sense to choose Azure Active Directory as the IDaaS provider for easy onboarding and single sign on.

There is a tonne of documentation provided by Microsoft already, so this is my attempt to pull together a snapshot of all the important pieces in one place. I also found current documentation and examples generally hard to follow, for example, some referenced the Azure Classic Portal and some the new Shiny New Portal.

Our application is a Single Page Application (SPA) comprising the following major components:

  • A React front end
  • Adal.js to handle auth flow in the front end
  • A .NET Core Web API using JwtBearerOptions to configure and handle JWTs
  • Microsoft Azure to host and configure the application

Architecture

There are several approaches when choosing a multi-tenanted architecture.

For reasons of cost and initial ease of setup as the business bootstraps, we chose row based tenant separation. Our application is not data-heavy, with IO kept to a minimum and most heavy lifting happening client-side. It is designed in such a way as to be evolvable if things change, for example, it would be pretty trivial to stand up a database per tenant.

JWT Authentication needs to be set up first in Startup.ConfigureServices:

 public void ConfigureDefaultServices(IServiceCollection services)
 {
  ...

  services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new 
  TokenValidationParameters
                    {
                        ValidateIssuer = false,
                        RoleClaimType = ClaimTypes.Role
                    };
                    options.RequireHttpsMetadata = !_environment.IsDevelopment();
                    options.Audience = Configuration["ida:Audience"];
                    options.Authority = 
  "https://login.microsoftonline.com/common/";
                    options.Events = new JwtBearerEvents
                    {
                       // subscribe to events like OnTokenValidated to perhaps add logging etc...
                    };
                });
  ...
}

In our AddJwtBearer configuration, we specify the 'Audience' of our application, which comes from appsettings.json. This is the ID of the application you need to register with Azure AD. More on this in the next section.

Next, we can use Dependency Injection to pass an IHttpContextAccessor service into our data access layer so we can access the current user principal and its claims.

In our case I dusted off the old (and often derided) Repository Pattern. The main reason for digging up this relic was to provide a wrapper over every database query for the current signed in tenant, keeping that logic in one place. We could have used a global filter, controller base class or some other technique, but this pattern was familiar to the team and was very easy to test.

Speaking of which, it goes without saying that a very robust set of unit and integration tests are in place to ensure all database queries either get by tenant ID or update/create by setting the tenant ID.

public class TenantRepository  
    {
        private readonly string _tenantId;

        public TenantRepository(
            DbContext context,
            IHttpContextAccessor contextAccessor
            )
        {
           ...
           _tenantId =    contextAccessor.HttpContext.User.FindFirstValue(Constants.TenantClaim);
           ...
        }
...
// all below database queries use the _tenantId set in the constructor.
...
}

Of course, we can't just have any Azure AD tenant signing in to our application, they need to buy a license first. The second major component then is our Tenant catalogue.

When clients purchase from us we sign in to our application as an internal user and add a new Tenant to our catalogue using the client's details, including their tenant ID which we ask of their company Systems Administrator.

We then add some authorization middleware, which ensures that the signed-in user is a registered tenant and performs other authorization checks, like which modules they have paid for and what application roles they are assigned to. I will go into the details of this in a future post.

Azure AD Application Configuration

The next thing to do is to register your application with Azure AD. In your tenant, go to the Azure Active Directory menu. You should see the following application management options.

As we are the author of the application, we need to choose 'App registrations'.

Here you need to choose the option 'Register an application my organisation is developing' then set the name, identifying URL, homepage, redirect/logout URLs and other settings relevant to your application like roles (via the manifest).

The most important thing to do here is set Multi-tenanted to Yes.

Now Active Directory is aware of our application and its rules regarding how users sign in. The Application ID that is created for you is what we used for the Audience property in our JWT configuration from our Startup.cs code above.

Adal.js

Adal.js is a client library provided by the Azure team to ease development of SPAs using Azure AD. The library is sadly geared toward Angular applications but it is simple enough to use with React or other frameworks. The implementation of the front end auth flow is worthy of another blog post, but for now, more information can be found here.

The main part we are concerned with is setting up the AuthenticationContext config

import AuthenticationContext = from "adal-angular";

const adalConfig = {  
  instance: "https://login.microsoftonline.com/",
  postLogoutRedirectUri: location.origin,
  redirectUri: location.origin,
  tenant: "common",
  clientId: <your registered application id>,
  extraQueryParameter: "nux=1",
  endpoints: {
    "https://graph.microsoft.com": "https://graph.microsoft.com",
  },
  cacheLocation: "localStorage",

};
const adal = new AuthenticationContext(adalConfig);

The important parts are setting the tenant to common for multi-tenant applications and setting the clientId to the application id from your registered application in Azure AD.

When your sign in/up flow is triggered in your application, the user will be redirected to the Microsoft sign in page. After they redirect to your app, adal.js will parse the token out of the browser address bar and put it into local storage for you.

The Other Side

When a user signs in for the first time, a consent flow is triggered. This flow can be configured in your application or sign up flow (admin or user consent).

This is yet another topic of a future blog post but lots of useful information can be found here.

Once signed in and consent approved, System Administrators from your paying customer's organisation should see your application drop into their Enterprise Applications/All Applications list in their Azure AD / Office 365 tenant.

It is here that the Administrator can assign users to the application and roles to users if needed.

Wrap up

And that completes the loop. In summary, we need to:

  • Build a SaaS application
  • Direct the user to the Microsoft sign in page via Adal.js
  • Configure and handle the JWT tokens in the API
  • Use Policy and Claims based security to protect our API and endpoints
  • Register the application with Azure AD - configure URLs, settings, roles and multi-tenancy
  • Profit

I hope this is a helpful digest of what is a sprawling subject and I will continuously update this post with anything I've missed.