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.microsoftonline.com/",
"ClientId": "[Enter_the_Application_Id_Here]",
"TenantId": "common",
// To call an API
"ClientCredentials": [
{
"SourceType": "ClientSecret",
"ClientSecret":"[Enter_the_Client_Secret_Here]"
}
]
},
"GraphBeta": {
"BaseUrl": "https://graph.microsoft.com/beta",
"Scopes": ["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.microsoftonline.com/",
"ClientId": "[Enter_the_Application_Id_Here]",
"TenantId": "common",
// To call an API
"ClientCredentials": [
{
"SourceType": "KeyVault",
"KeyVaultUrl": "https://msidentitywebsamples.vault.azure.net",
"KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
}
]
},
"GraphBeta": {
"BaseUrl": "https://graph.microsoft.com/beta",
"Scopes": ["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:
- Add the Microsoft.Identity.Web.GraphServiceClient NuGet package to the project.
- 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
- Add the Microsoft.Identity.Web.DownstreamApi NuGet package to the project.
- 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 callMyApiScope
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": "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:
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.