Skip to main content

Restricted Access on Azure App Service API endpoints using Managed Identity, App Registration and Authentication Settings

·1694 words·8 mins
Bemn
Author
Bemn
Hong Konger.

Overview
#

One of the projects I worked on recently is a new .NET API service (let’s call it Callee) that runs on Azure using App Service. The client wants to implement a simple authentication solution to protect the API endpoints. The solution should only allow certain Azure app services, Azure Virtual Desktop instances and developers (let’s call them Caller(s)) under same Azure Subscription to access the Callee.

The following diagram illustrates the simplified authentication flow:

sequenceDiagram actor dev as Client (Caller) participant guard as Azure App Service (Callee) note over dev: Developer or
Consumer App Service dev->>guard: makes an API request
(with object ID of the Azure resource) guard->>guard: checks authentication settings alt allowed create participant producer as Protected API endpoint guard->>producer: relays API request destroy producer producer->>dev: βœ… 200 OK else unauthorized guard->>dev: ❌ 401 Unauthorized end

Terminologies
#

We describe the protected .NET API service as the “Callee”, while the client(s) that consuming the API resource are the “Caller(s)”.

TermDefinition
CalleeThe receiver or provider of the API request. It exposes the endpoint and handles the logic for fulfilling the request.
CallerThe client or initiator of the API request. It makes a call to another service to request data or trigger an action.

Configurations on Azure
#

The overall idea is to set up the authentication settings on Callee. We only allow HTTP requests from Caller(s) that contain a bearer access token with specific properties. The access token should be a JWT with allowed combination(s) of aud and oid values.

Caller
#

Create an App Service called e.g. poc-web-caller. Caller need to provide the identities and audiences to Callee in order to add them into allowed list.

For an Azure App Service
#

  1. Go to Caller’s Azure App Service. In the left menu, choose Settings > Identity
  2. Turn on the system assigned managed identity:
    img/caller-system-mi.png
  3. Keep the Object (principal) ID
  4. Alternatively, you can choose user-assigned managed identity Object (principal) ID and assign the identity created:
    img/caller-user-mi-step1.png
    img/caller-user-mi-step2.png

For an Azure Virtual Desktop instance
#

You will need to find out the Object (Principal) ID of the AVD instance.

[!NOTE] Require administrator access You need administrator access to install Azure CLI

Install Azure CLI in PowerShell (run as administrator): https://learn.microsoft.com/en-us/cli/azure/install-azure-cli-windows?view=azure-cli-latest

winget install -e --id microsoft.AzureCLI

Start PowerShell as a regular user. Sign in to Azure and choose the correct subscription:

az login

Get the Object (principal) ID of the AVD instance:

az ad sp list --display-name "$Env:computername" --query "[0].id" -o tsv

For an Azure Account user using Visual Studio
#

[!NOTE] This approach also applies to a Mac or Linux user having Azure CLI installed

You will need to find out the Object (Principal) ID of your active account. Just like getting the ID from an AVD, you need to install Azure CLI.

Sign in to Azure and get the Object (principal) ID of the signed in user:
```pwsh
az login
az ad signed-in-user show --output tsv --query id

Callee
#

Create an App Service called e.g. poc-web-callee.

App Registration
#

[!NOTE] You need to create a new app registration if you don’t have one.

  1. Go to App Registration. Click New registration.
  2. In your new registration:
    1. Name: user-facing display name for this application
    2. Supported account types: We choose Default Directory only - Single tenant in this example
    3. Redirect URI: leave it empty
  3. Click Register

Azure App Service
#

  1. Go to Callee’s Azure Web Service. In the left menu, choose Settings > Authentication
  2. Click Add provider. Choose Microsoft.
    img/callee-auth-step1.png
    img/callee-auth-step2.png
  3. Fill in the details:
    1. Choose a tenant for your application and its users: Workforce configuration (current tenant)
    2. App registration
      1. App registration type: Pick an existing app registration in this directory
      2. Select the app registration you’ve created in Name or app ID
      3. Client secret expiration: choose the desired secret lifespan
    3. Additional checks
      1. Client application requirement: Allow requests from any application
      2. Allowed client applications (allowed audiences):
        1. the app registration app ID you chose (for Azure app service)
        2. https://management.core.windows.net/ (for localhost development)
      3. Identity requirement: Allow requests from specific identities
      4. Allowed identities: fill in one or some of the following items depends on your need:
        1. Caller’s user assigned managed identity Object (principal ID)
        2. Caller’s system-assigned managed identity Object (principal ID)
        3. (For localhost development) Azure Account’s Object (principal) ID you got in the Caller configuration mentioned in previous section.
      5. Tenant requirement: Allow requests only from the issuer tenant
    4. App Service authentication settings
      1. Restrict access: Require authentication
      2. Unauthenticated requests: HTTP 401 Unauthorized: recommended for APIs
      3. Token store: checked
  4. Click Add to add the identity provider
  5. Click the Edit button next to Authentication settings, or the edit icon next to the identity provider to change the settings once it’s created:
    img/callee-auth-step3.png

The table below summarizes the information we need to configure the Callee’s Authentication settings:

EnvironmentObject ID (oid)Audience (aud)
AzureApp service’s managed identityCallee’s app registration ID
AVDMachine’s managed identityCallee’s app registration ID
DeveloperPersonal access tokenhttps://management.core.windoes.net/

Next, we will show some key code snippets on Caller. We will also create and deploy the sample Caller and Callee applications to show the authentication in action.

Source code
#

A minimal example for caller and callee applications can be found in this GitHub:

bemnlam/azure-managed-identity-auth

A minimal example showing how to protect an Azure app service using App Registration and Managed Identity

C#
0
0

Here are some key points I want to highlight:

Caller
#

TokenCredential
#

A TokenCredential singleton can be created in app start:

// In Startup.cs or Program.cs. 
// You should create different ChainedTokenCredential for different environments.
// This is a minimal example for demo purpose only.
builder.Services.AddSingleton<TokenCredential>(serviceProvider => {
  var objId = "YOUR_MANAGED_IDENTITY_OBJECT_ID";
  return new ChainedTokenCredential(
    new AzureCliCredential(), // for development only
    (string.IsNullOrEmpty(objId) ? 
	    new ManagedIdentityCredential() : // system assigned managed identity
	    new ManagedIdentityCredential(
		    ManagedIdentityId.FromUserAssignedObjectId(objId)
		) // user-assigned managed identity
	)
  );
});

You need to choose different credential provider under different environments.

EnvironmentRecommended CredentialReason
AzureManagedIdentityCredential(ManagedIdentityId)1. An app service should have the managed identity.
2. You should provide it explicitly in order to avoid misuse of other managed identities.
AVD (Azure Virtual Desktop)ManagedIdentityCredential()An AVD as a Azure resource under the same subscription should have a system assigned managed identity.
Visual Studio with Azure AccountVisualStudioCredential()Use the identity same as the user signed in in Visual Studio so that the Callee knows the Caller’s identity.
MacAzureCliCredentialThis is one of the clients you can use in Mac.
Note on DefaultAzureCredential and ChainedTokenCredential
#

I prefer using ChainedTokenCredential, which allows me to choose which credential(s) to use, and define the attempt sequence that fits my requirement.

You may also consider using DefaultAzureCredential in non-production environments. However, using this might give you unexpected result because:

  1. the attempt sequence might not in the desired order
  2. you may not know which credential is picked eventually, unless you configure and inspect the logs

For example, if a developer signed in to Azure account in Visual Studio on an Azure Virtual Desktop instance, DefaultAzureCredential will attempt to authenticate with ManagedIdentityCredential before VisualStudioCredential. If the app want to use developer’s personal identity over machine’s identity, this will not work as expected.

Access Token
#

The scope of the access token which Caller is going to generate is the Callee’s app ID. Get an access token and add it as the Bearer authorization token when making API request to the Callee:

// In Program.cs. You can also use the MVC pattern.
app.MapGet("/callee/protected-resource", async (IConfiguration configuration, TokenCredential credential) =>
{
	var myScope = "CALLEE_APP_REGISTRATION_ID";
	AccessToken token = await credential.GetTokenAsync(
		new TokenRequestContext(
			scopes: new[] { myScope }), 
			new CancellationTokenSource().Token
		)
	);
	using var httpClient = new HttpClient();
	httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token.Token);
	// make an HTTP request to a protected API endpoint of the Callee...
}

Callee
#

Nothing special about the callee. Make sure sure that Azure is configured properly and let the Caller(s) know your Azure App Registration ID.

Demo
#

Checkout the minimal GitHub repository:

bemnlam/azure-managed-identity-auth

A minimal example showing how to protect an Azure app service using App Registration and Managed Identity

C#
0
0

If you the following tools installed:

Callee
#

  • Open poc-callee in VS Code
  • In VS Code, open the Command Palette and select Azure: Deploy to Web App…
    img/callee-deploy-step1.png
  • Choose poc-callee, add config and choose the Azure App Service:
    img/callee-deploy-step2.png
    img/callee-deploy-step3.png
  • Expected result for a successful deployment:
    img/callee-deploy-succeeded.png
  • Open Command Palette and select Azure App Service: Browse Website:
    img/callee-deploy-browse.png
  • You should see a webpage with a message You do not have permission to view this directory or page. That is expected because you don’t have a valid access token.

Caller
#

  • Open poc-caller in VS Code
  • Modify appsettings.json:
    • CalleeApi: the root url of the Callee e.g. https://{callee-app-name}-{random-hash}.{region}-01.azurewebsites.net/
    • CalleeAppRegistrationId: the App Registration ID of the Callee
    • ManagedIdentity: Caller’s user/system-assigned managed identity Object (Principal) ID
  • Choose poc-caller, add the config and choose the Azure App Service:
    img/caller-deploy-step1.png
  • Expected result for a successful deployment:
    img/caller-deploy-succeeded.png
  • Choose Azure App Service: Browse Website in Command Palette. You should be able to see a Swagger UI.
  • Call GET /remote-ping endpoint and you should see a 200 response. The response body should contains a greeting message and a token:
    img/demo-result-succeeded.png

Local Development
#

If you want to run Caller locally, and call the Callee API endpoints which hosting on Azure, you will need to:

  1. Get your personal Object (principal) ID (if you are using Azure CLI or signed in user in Visual Studio)
  2. Add the Object (principal) ID to Callee’s allowed identities
  3. Add https://management.core.windows.net/ to Callee’s Allowed token audiences

Limitations
#

This is a simple, all-or-nothing authentication solution for the Callee app. In other words, you can’t make certain endpoints public while the others keep protected. Also, there will be some code changes in order to let all Caller apps be able to get an Azure access token.

Conclusion
#

In this post, we demonstrated how to secure Azure App Service API endpoints using Managed Identity, App Registration, and built-in Authentication settings. This approach enables access control across services and environments within the same Azure subscription without requiring custom authentication logic. While simple and effective, it’s best suited for internal APIs where all access can be gated uniformly.

References
#

Related

Avoid Multiple Lifecycle Hooks in Azure Devops Deployment Job
·541 words·3 mins
Optimizing Azure DevOps pipelines by minimizing lifecycle hooks improves variable consistency across deployment stages.
Documentation Makes Easy With MkDocs and GitLab Pages
·1045 words·5 mins
A guide to build a static website using MkDocs and deploy it to GitLab Pages.
🐞 Solving 'Not Enough Space' issue in Atlassian Bamboo using Junction Link
·483 words·3 mins
Learn how to resolve Bamboo’s “Not Enough Space” issue by optimizing disk usage, leveraging external drives, and automating cleanup tasks.