A web API that calls web APIs: Code configuration

This article describes how to configure code for a Web API app using the OAuth 2.0 authorization code flow.

Microsoft recommends that you use the Microsoft.Identity.Web NuGet package when developing an ASP.NET Core protected API calling downstream web APIs. See Protected web API: Code configuration | Microsoft.Identity.Web for a quick presentation of that library in the context of a web API.

Prerequisites

Configure the app

Choose a language for your web API.

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.

Program.cs

using Microsoft.Identity.Web;

// ...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration, Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();
// ...

A web API needs to acquire a token for the downstream API. Specify it by adding the .EnableTokenAcquisitionToCallDownstreamApi() line after .AddMicrosoftIdentityWebApi(Configuration). This line exposes the ITokenAcquisition service that can be used in the controller/pages actions.

However, an alternative method is to implement a token cache. For example, adding .AddInMemoryTokenCaches(), to Program.cs allows the token to be cached in memory.

using Microsoft.Identity.Web;

// ...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration, Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();
// ...

Microsoft.Identity.Web provides two mechanisms for calling a downstream web API from another API. The option you choose depends on whether you want to call Microsoft Graph or another API.

Option 1: Call Microsoft Graph

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

Note

There is an ongoing issue for Microsoft Graph SDK v5+. For more information, refer to the GitHub issue.

To expose Microsoft Graph:

  1. Add the Microsoft.Identity.Web.GraphServiceClient NuGet package to the project.
  2. Add .AddMicrosoftGraph() after .EnableTokenAcquisitionToCallDownstreamApi() in Program.cs. .AddMicrosoftGraph() has several overrides. Using the override that takes a configuration section as a parameter, the code becomes:
using Microsoft.Identity.Web;

// ...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration, Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph(Configuration.GetSection("GraphBeta"))
    .AddInMemoryTokenCaches();
// ...

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

  1. Add the Microsoft.Identity.Web.DownstreamApi NuGet package to the project.
  2. Add .AddDownstreamApi() after .EnableTokenAcquisitionToCallDownstreamApi() in Program.cs. The code becomes:
using Microsoft.Identity.Web;

// ...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("MyApi", Configuration.GetSection("MyApiScope"))
    .AddInMemoryTokenCaches();
// ...

where;

  • MyApi denotes the name of the downstream web API that your web API intends to call
  • MyApiScope is the scope necessary for your web API to request in order to interact with the downstream web API

These values will be represented in your JSON that will be similar to the following snippet.

"DownstreamAPI": {
      "BaseUrl": "https://downstreamapi.contoso.com/",
      "Scopes": "https://microsoftgraph.chinacloudapi.cn/user.read"
    },

If the web app needs to call another API resource, repeat the .AddDownstreamApi() method with the relevant scope as shown in the following snippet:

using Microsoft.Identity.Web;

// ...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(Configuration, "AzureAd")
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamApi("MyApi", Configuration.GetSection("MyApiScope"))
    .AddDownstreamApi("MyApi2", Configuration.GetSection("MyApi2Scope"))
    .AddInMemoryTokenCaches();
// ...

Note that .EnableTokenAcquisitionToCallDownstreamApi is called without any parameter, which means that the access token is acquired just in time as the controller requests the token by specifying the scope.

The scope can also be passed in when calling .EnableTokenAcquisitionToCallDownstreamApi, which would make the web app acquire the token during the initial user login itself. The token will then be pulled from the cache when controller requests it.

Similar to web apps, various token cache implementations can be chosen. For details, see Microsoft identity web - Token cache serialization on GitHub.

The following image shows the possibilities of Microsoft.Identity.Web and the impact on Program.cs:

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.

You can also see an example of OBO flow implementation in Node.js and Azure Functions.

Protocol

For more information about the OBO protocol, see the Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow.

Next step