A web app that calls web APIs: Code configuration

In the previous article, you registered an application in Microsoft Entra. This article will show you how to configure the application code, and modify your web app so that it not only signs users in but also now calls web APIs. The application you create uses the OAuth 2.0 authorization code flow to sign the user in. This flow has two steps:

  1. Request an authorization code. This part delegates a private dialogue with the user to the Microsoft identity platform. During that dialogue, the user signs in and consents to the use of web APIs. When the private dialogue ends successfully, the web app receives an authorization code on its redirect URI.
  2. Request an access token for the API by redeeming the authorization code.

Prerequisites

Microsoft libraries supporting web apps

The following Microsoft libraries support web apps:

Language / framework Project on
GitHub
Package Getting
started
Sign in users Access web APIs Generally available (GA) or
Public preview1
.NET MSAL.NET Microsoft.Identity.Client Library cannot request ID tokens for user sign-in. Library can request access tokens for protected web APIs. GA
.NET Microsoft.IdentityModel Microsoft.IdentityModel Library cannot request ID tokens for user sign-in.2 Library cannot request access tokens for protected web APIs.2 GA
ASP.NET Core ASP.NET Core Microsoft.AspNetCore.Authentication Quickstart Library can request ID tokens for user sign-in. Library cannot request access tokens for protected web APIs. GA
ASP.NET Core Microsoft.Identity.Web Microsoft.Identity.Web Quickstart Library can request ID tokens for user sign-in. Library can request access tokens for protected web APIs. GA
Java MSAL4J msal4j Quickstart Library can request ID tokens for user sign-in. Library can request access tokens for protected web APIs. GA
Spring spring-cloud-azure-starter-active-directory spring-cloud-azure-starter-active-directory Tutorial Library can request ID tokens for user sign-in. Library can request access tokens for protected web APIs. GA
Node.js MSAL Node msal-node Quickstart Library can request ID tokens for user sign-in. Library can request access tokens for protected web APIs. GA
Python MSAL Python msal Library can request ID tokens for user sign-in. Library can request access tokens for protected web APIs. GA
Python identity identity Quickstart Library can request ID tokens for user sign-in. Library can request access tokens for protected web APIs. --

(1) Universal License Terms for Online Services apply to libraries in Public preview.

(2) The Microsoft.IdentityModel library only validates tokens - it can't request ID or access tokens.

Select the tab for the platform you're interested in:

Client secrets or client certificates

Given that your web app now calls a downstream web API, provide a client secret or client certificate in the appsettings.json file. You can also add a section that specifies:

  • The URL of the downstream web API
  • The scopes required for calling the API

In the following example, the GraphBeta section specifies these settings.

{
  "AzureAd": {
    "Instance": "https://login.partner.microsoftonline.cn/",
    "ClientId": "[Enter_the_Application_Id_Here]",
    "TenantId": "common",

   // To call an API
   "ClientCredentials": [
    {
      "SourceType": "ClientSecret",
      "ClientSecret":"[Enter_the_Client_Secret_Here]"
    }
  ]
 },
 "GraphBeta": {
    "BaseUrl": "https://microsoftgraph.chinacloudapi.cn/beta",
    "Scopes": ["https://microsoftgraph.chinacloudapi.cn/user.read"]
    }
}

Note

You can propose a collection of client credentials, including a credential-less solution like workload identity federation for Azure Kubernetes. Previous versions of Microsoft.Identity.Web expressed the client secret in a single property "ClientSecret" instead of "ClientCredentials". This is still supported for backwards compatibility but you cannot use both the "ClientSecret" property, and the "ClientCredentials" collection.

Instead of a client secret, you can provide a client certificate. The following code snippet shows using a certificate stored in Azure Key Vault.

{
  "AzureAd": {
    "Instance": "https://login.partner.microsoftonline.cn/",
    "ClientId": "[Enter_the_Application_Id_Here]",
    "TenantId": "common",

   // To call an API
   "ClientCredentials": [
      {
        "SourceType": "KeyVault",
        "KeyVaultUrl": "https://msidentitywebsamples.vault.azure.cn",
        "KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
      }
   ]
  },
  "GraphBeta": {
    "BaseUrl": "https://microsoftgraph.chinacloudapi.cn/beta",
    "Scopes": ["https://microsoftgraph.chinacloudapi.cn/user.read"]
  }
}

Warning

If you forget to change the Scopes to an array, when you try to use the IDownstreamApi the scopes will appear null, and IDownstreamApi will attempt an anonymous (unauthenticated) call to the downstream API, which will result in a 401/unauthenticated.

Microsoft.Identity.Web provides several ways to describe certificates, both by configuration or by code. For details, see Microsoft.Identity.Web - Using certificates on GitHub.

Modify the Startup.cs file

Your web app needs to acquire a token for the downstream API. You specify it by adding the .EnableTokenAcquisitionToCallDownstreamApi() line after .AddMicrosoftIdentityWebApp(Configuration). This line exposes the IAuthorizationHeaderProvider service that you can use in your controller and page actions. However, as you see in the following two options, it can be done more simply. You also need to choose a token cache implementation, for example .AddInMemoryTokenCaches(), in Startup.cs:

using Microsoft.Identity.Web;

public class Startup
{
  // ...
  public void ConfigureServices(IServiceCollection services)
  {
  // ...
  services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
          .AddMicrosoftIdentityWebApp(Configuration, "AzureAd")
            .EnableTokenAcquisitionToCallDownstreamApi(new string[]{"https://microsoftgraph.chinacloudapi.cn/user.read" })
            .AddInMemoryTokenCaches();
   // ...
  }
  // ...
}

The scopes passed to EnableTokenAcquisitionToCallDownstreamApi are optional, and enable your web app to request the scopes and the user's consent to those scopes when they sign in. If you don't specify the scopes, Microsoft.Identity.Web enables an incremental consent experience.

Microsoft.Identity.Web offers two mechanisms for calling a web API from a web app without you having to acquire a token. The option you choose depends on whether you want to call Microsoft Graph or another API.

Option 1: Call Microsoft Graph

If you want to call Microsoft Graph, Microsoft.Identity.Web enables you to directly use the GraphServiceClient (exposed by the Microsoft Graph SDK) in your API actions. To expose Microsoft Graph:

  1. Add the Microsoft.Identity.Web.GraphServiceClient NuGet package to your project.

  2. Add .AddMicrosoftGraph() after .EnableTokenAcquisitionToCallDownstreamApi() in the Startup.cs file. .AddMicrosoftGraph() has several overrides. Using the override that takes a configuration section as a parameter, the code becomes:

    using Microsoft.Identity.Web;
    
    public class Startup
    {
      // ...
      public void ConfigureServices(IServiceCollection services)
      {
      // ...
      services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
              .AddMicrosoftIdentityWebApp(Configuration, "AzureAd")
                .EnableTokenAcquisitionToCallDownstreamApi(new string[]{"https://microsoftgraph.chinacloudapi.cn/user.read" })
                   .AddMicrosoftGraph(Configuration.GetSection("GraphBeta"))
                .AddInMemoryTokenCaches();
       // ...
      }
      // ...
    }
    

Option 2: Call a downstream web API other than Microsoft Graph

If you want to call an API other than Microsoft Graph, Microsoft.Identity.Web enables you to use the IDownstreamApi interface in your API actions. To use this interface:

  1. Add the Microsoft.Identity.Web.DownstreamApi NuGet package to your project.

  2. Add .AddDownstreamApi() after .EnableTokenAcquisitionToCallDownstreamApi() in the Startup.cs file. .AddDownstreamApi() has two arguments, and is shown in the following snippet:

    • The name of a service (API), which is used in your controller actions to reference the corresponding configuration
    • a configuration section representing the parameters used to call the downstream web API.
    using Microsoft.Identity.Web;
    
    public class Startup
    {
      // ...
      public void ConfigureServices(IServiceCollection services)
      {
      // ...
      services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
              .AddMicrosoftIdentityWebApp(Configuration, "AzureAd")
                .EnableTokenAcquisitionToCallDownstreamApi(new string[]{"https://microsoftgraph.chinacloudapi.cn/user.read" })
                   .AddDownstreamApi("MyApi", Configuration.GetSection("GraphBeta"))
                .AddInMemoryTokenCaches();
       // ...
      }
      // ...
    }
    

Summary

As with web APIs, you can choose various token cache implementations. For details, see Microsoft.Identity.Web - Token cache serialization on GitHub.

The following image shows the various possibilities of Microsoft.Identity.Web and their effect on the Startup.cs file:

Block diagram showing service configuration options in startup dot C S for calling a web API and specifying a token cache implementation

Note

To fully understand the code examples here, be familiar with ASP.NET Core fundamentals, and in particular with dependency injection and options.

Code that redeems the authorization code

Microsoft.Identity.Web simplifies your code by setting the correct OpenID Connect settings, subscribing to the code received event, and redeeming the code. No extra code is required to redeem the authorization code. See Microsoft.Identity.Web source code for details on how this works.

Instead of a client secret, the confidential client application can also prove its identity by using a client certificate or a client assertion. The use of client assertions is an advanced scenario, detailed in Client assertions.

Token cache

Important

The token-cache implementation for web apps or web APIs is different from the implementation for desktop applications, which is often file based. For security and performance reasons, it's important to ensure that for web apps and web APIs there is one token cache per user account. You must serialize the token cache for each account.

The ASP.NET core tutorial uses dependency injection to let you decide the token cache implementation in the Startup.cs file for your application. Microsoft.Identity.Web comes with prebuilt token-cache serializers described in Token cache serialization. An interesting possibility is to choose ASP.NET Core distributed memory caches:

// Use a distributed token cache by adding:
    services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAd")
            .EnableTokenAcquisitionToCallDownstreamApi(
                initialScopes: new string[] { "https://microsoftgraph.chinacloudapi.cn/user.read" })
            .AddDistributedTokenCaches();

// Then, choose your implementation.
// For instance, the distributed in-memory cache (not cleared when you stop the app):
services.AddDistributedMemoryCache();

// Or a Redis cache:
services.AddStackExchangeRedisCache(options =>
{
 options.Configuration = "localhost";
 options.InstanceName = "SampleInstance";
});

// Or even a SQL Server token cache:
services.AddDistributedSqlServerCache(options =>
{
 options.ConnectionString = _config["DistCache_ConnectionString"];
 options.SchemaName = "dbo";
 options.TableName = "TestCache";
});

For details about the token-cache providers, see also Microsoft.Identity.Web's Token cache serialization article, and the ASP.NET Core web app tutorials | Token caches phase of the web apps tutorial.

Next step

At this point, when the user signs in, a token is stored in the token cache. Let's see how it's then used in other parts of the web app.