Restrict access to Single Page Apps using Azure Static Web Apps and Entra ID

You want only specific users or groups in your organization to have access to your frontend-only app? This post explains how to use Microsoft Entra ID (formerly know as Azure Active Directory) to provide enterprise grade security in authentication and authorization of users and security groups in single page apps like Angular.

Philipp Bauknecht
Published in
6 min readJul 24, 2023

--

I just came across this customer requirement in one of our projects:

We want to make our shared component library built with Storybook and Angular available only to some users within our organization.

What sounds simple on first glance is a bit tricky: Since single page apps run on the client it’s impossible to enforce authorization. This is typically the responsibility for a backend service. Here comes Azure Static Web Apps to the rescue! This cloud service allows for easy and inexpensive hosting of single page apps like Angular in the cloud and offers some nice extras. One of these extras is configuration based authentication (know the user) and route authorization (decide whether a user can access a resource). Route based authorization let’s you decide whether a route (e.g. an app’s index.html) gets served to a client based on a user being present and a user being member of a role e.g. the role “authenticated users”.

The default authentication configuration of Azure Static Web Apps just cares if a user from any of the available identity providers like Entra ID, GitHub or Twitter is present. This means anyone who has an account with any of these can authenticate and get access. When we’re building an app for a specific organization we typically want to restrict a) to a specific tenant in Entra ID and block other tenants, GitHub and Twitter and b) to specific users or security groups within the tenant when we’re dealing with larger enterprises. To achieve this Azure Static Web App offer custom authentication: https://learn.microsoft.com/en-us/azure/static-web-apps/authentication-custom?tabs=aad%2Cinvitations#manage-roles

This article walks you through the steps required to prepare an Azure Cloud environment, setup the required infrastructure as code using Bicep, configure a Static Web App to use custom authentication and use Azure Pipeline in Azure DevOps to deploy everything.

Step 1: Prepare the Cloud Environment

We’ll use the Azure CLI to setup our environment. Therefore we need some variables to get started:

Next we can use this data, to login, set the subscription to use and create a resource group for our solution:

To deploy our infrastructure and code using a Azure Pipeline, this pipeline will need it’s own identity to authenticate with Azure through a Service Connection in Azure DevOps. Therefore we need to create a new Service Principle that is a member of the Contributor and User Access Administrator roles in our newly created resource group:

Note that we need an extra call az ad sp show to retrieve the clientId. User Access Administrator role is required so that the sp can add other resources into specific roles later.

With this information we can now create a new Service Connection in Azure DevOps through `Project settings` > `Service connection` > `New service connection` > `Azure Resource Manager` > `Service principal (manual)`. We can output all required inputs in the previous terminal session:

App Registration for Static Web Apps

In able to restrict authentication to users of our own tenant we need to create a new app registration in our Entra ID (aka Azure Active Directory) tenant. This app registration needs it’s own client secret and also needs to have a service principal. The service principal enables using assignments throught the enterprise apps feature. Assignment will later be used to further restrict access to specific users or groups:

Finally let’s create a new environment with the same name than the service connection e.g. securengdemo-dev in Azure Pipelines and also a new variable group with the same name and these values:

Now our Environment is prepared both in Azure and in Azure DevOps.

Step 2: Create the Infrastructure

Our infrastructure is pretty straightforward → We need to have a Azure Static Web App:

Since this app will need a secret (the APP_CLIENT_SECRET) as configuration variable we also need to provide a place to securely store this in a Azure Key Vault:

In the Key Vault we will store secrets:

The Azure Static Web App instance needs to be able to read secrets from the Key Vault so we need to give this permission by assigning a role using RBAC (Role based Access Control):

Now that we have all required infrastructure modules in place we can put them together in a main.bicep:

This bicep file creates both a Static Web App and a Key Vault, grants access to the Key Vault for the Static Web App, stores 2 secrets in the vault and provides a link to those secrets as app configuration to the Static Web App.

Since we need to know the Static Web App’s name later for the deployment of our code, it’s returned as an output variable. To use output variables of bicep in a Azure Pipeline it’s required to call the bicep through a script file, rather than directly:

The last line adds the output variable to the task variables of the calling pipeline tasks. This way we can read it in following pipeline tasks.

Step 3: Create a simple Angular App for Testing

Create any kind of Single Page Application e.g. an empty Angular app for testing.

The interesting part here is the configuration of the Static Web App.

  • Here we can define which areas of our app should be restricted to authenticated users through route definitions. In our case we want the entire app to be restricted.
  • We can enabled local navigation through the app’s router using a navigationFallback strategy.
  • We can automatically redirect unauthenticated users to our login page using responseOverrides for http status 401.
  • We can define custom authentication (and thus disable default any tenant, GitHub and Twitter authentication) by defining a custom identity provider.

So let’s see how this look all together:

Don’t forget to put in your Tenant Id as part of the openIdIssuer url.

Step 4: Create a Pipeline

To automate deployment of both the infrastructure and the app itself we’ll use Azure Pipelines.

After running the pipeline successfully for the first time we need to add the callback url of our Static Web App to the allowed redirect URIs of the app registration. We can only do this after the Static Web App has been deployed for the first time as we need to get it’s hostname:

So let’s copy that URL and go to the authentication section of the app registration we created during the infrastructure setup. Here we need to add web platform and then the url followed by /.auth/login/aad/callback:

Now we have successfully restricted access. We we open the app we will be redirected to a Entra ID login page and only user’s from our tenant can sign in.

Restrict to security groups

When working in a larger enterprise we want to further limit access e.g. to only specifc security groups. Therefore we need to find our app registration under “enterprise apps” in the Entra ID portal by searching for it’s object id and then enable “Assignment required”:

Switch over to “Users and groups” to assign specific users or groups to this app. Everyone else from the same tenant will be denied access.

Conclusion

With very little configuration in the staticwebapp.config.json it’s possible to effectively enable access restriction for single page apps to specific Entra ID tenants and further to security groups using enterprise app assignments.

--

--

Philipp Bauknecht

CEO @ medialesson. Microsoft Regional Director & MVP Windows Development. Father of identical twins. Passionate about great User Interfaces, NYC & Steaks