Skip to content

Commit 89633d7

Browse files
Merge pull request #235332 from jmprieur/jmprieur/webappIdWeb2x
Update web app how to, to IdWeb 2x
2 parents 39d8ebc + b422393 commit 89633d7

6 files changed

+428
-257
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
---
2+
title: Client credentials for web apps and web APIs calling downstream APIs
3+
description: Include file with a common configuration in web apps and web APIs calling downstream web APIs (ASP.NET Core and ASP.NET OWIN).
4+
services: active-directory
5+
author: jmprieur
6+
manager: CelesteDG
7+
8+
ms.service: active-directory
9+
ms.subservice: develop
10+
ms.workload: identity
11+
ms.topic: include
12+
ms.date: 05/08/2023
13+
ms.author: jmprieur
14+
ms.reviewer: jmprieur
15+
ms.custom: aaddev
16+
---
17+
18+
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:
19+
20+
- The URL of the downstream web API
21+
- The scopes required for calling the API
22+
23+
In the following example, the `GraphBeta` section specifies these settings.
24+
25+
```JSON
26+
{
27+
"AzureAd": {
28+
"Instance": "https://login.microsoftonline.com/",
29+
"ClientId": "[Enter_the_Application_Id_Here]",
30+
"TenantId": "common",
31+
32+
// To call an API
33+
"ClientCredentials": [
34+
{
35+
"SourceType": "ClientSecret",
36+
"ClientSecret":"[Enter_the_Client_Secret_Here]"
37+
}
38+
]
39+
},
40+
"GraphBeta": {
41+
"BaseUrl": "https://graph.microsoft.com/beta",
42+
"Scopes": "user.read"
43+
}
44+
}
45+
```
46+
> [!NOTE]
47+
> You can propose a collection of client credentials, including a credential-less solution like workload identity federation for Azure Kubernetes.
48+
> 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.
49+
50+
Instead of a client secret, you can provide a client certificate. The following code snippet shows using a certificate stored in Azure Key Vault.
51+
52+
```JSON
53+
{
54+
"AzureAd": {
55+
"Instance": "https://login.microsoftonline.com/",
56+
"ClientId": "[Enter_the_Application_Id_Here]",
57+
"TenantId": "common",
58+
59+
// To call an API
60+
"ClientCredentials": [
61+
{
62+
"SourceType": "KeyVault",
63+
"KeyVaultUrl": "https://msidentitywebsamples.vault.azure.net",
64+
"KeyVaultCertificateName": "MicrosoftIdentitySamplesCert"
65+
}
66+
]
67+
},
68+
"GraphBeta": {
69+
"BaseUrl": "https://graph.microsoft.com/beta",
70+
"Scopes": "user.read"
71+
}
72+
}
73+
```
74+
75+
*Microsoft.Identity.Web* provides several ways to describe certificates, both by configuration or by code. For details, see [Microsoft.Identity.Web - Using certificates](https://github.com/AzureAD/microsoft-identity-web/wiki/Using-certificates) on GitHub.

articles/active-directory/develop/scenario-web-app-call-api-acquire-token.md

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ ms.custom: aaddev
1818

1919
# A web app that calls web APIs: Acquire a token for the app
2020

21-
You've built your client application object. Now, you'll use it to acquire a token to call a web API. In ASP.NET or ASP.NET Core, calling a web API is done in the controller:
21+
You've built your client application object. Now, you use it to acquire a token to call a web API. In ASP.NET or ASP.NET Core, calling a web API is done in the controller:
2222

2323
- Get a token for the web API by using the token cache. To get this token, you call the Microsoft Authentication Library (MSAL) `AcquireTokenSilent` method (or the equivalent in Microsoft.Identity.Web).
2424
- Call the protected API, passing the access token to it as a parameter.
@@ -27,27 +27,27 @@ You've built your client application object. Now, you'll use it to acquire a tok
2727

2828
*Microsoft.Identity.Web* adds extension methods that provide convenience services for calling Microsoft Graph or a downstream web API. These methods are explained in detail in [A web app that calls web APIs: Call an API](scenario-web-app-call-api-call-api.md). With these helper methods, you don't need to manually acquire a token.
2929

30-
If, however, you do want to manually acquire a token, the following code shows an example of using *Microsoft.Identity.Web* to do so in a home controller. It calls Microsoft Graph using the REST API (instead of the Microsoft Graph SDK). To get a token to call the downstream API, you inject the `ITokenAcquisition` service by dependency injection in your controller's constructor (or your page constructor if you use Blazor), and you use it in your controller actions, getting a token for the user (`GetAccessTokenForUserAsync`) or for the application itself (`GetAccessTokenForAppAsync`) in a daemon scenario.
30+
If, however, you do want to manually acquire a token, the following code shows an example of using *Microsoft.Identity.Web* to do so in a home controller. It calls Microsoft Graph using the REST API (instead of the Microsoft Graph SDK). Usually, you don't need to get a token, you need to build an Authorization header that you add to your request. To get an authorization header, you inject the `IAuthorizationHeaderProvider` service by dependency injection in your controller's constructor (or your page constructor if you use Blazor), and you use it in your controller actions. This interface has methods that produce a string containing the protocol (Bearer, Pop, ...) and a token. To get an authorization header to call an API on behalf of the user, use (`CreateAuthorizationHeaderForUserAsync`). To get an authorization header to call a downstream API on behalf of the application itself, in a daemon scenario, use (`CreateAuthorizationHeaderForAppAsync`).
3131

3232
The controller methods are protected by an `[Authorize]` attribute that ensures only authenticated users can use the web app.
3333

3434
```csharp
3535
[Authorize]
3636
public class HomeController : Controller
3737
{
38-
readonly ITokenAcquisition tokenAcquisition;
38+
readonly IAuthorizationHeaderProvider authorizationHeaderProvider;
3939

40-
public HomeController(ITokenAcquisition tokenAcquisition)
40+
public HomeController(IAuthorizationHeaderProvider authorizationHeaderProvider)
4141
{
42-
this.tokenAcquisition = tokenAcquisition;
42+
this.authorizationHeaderProvider = authorizationHeaderProvider;
4343
}
4444

4545
// Code for the controller actions (see code below)
4646
4747
}
4848
```
4949

50-
The `ITokenAcquisition` service is injected by ASP.NET by using dependency injection.
50+
ASP.NET Core makes `IAuthorizationHeaderProvider` available by dependency injection.
5151

5252
Here's simplified code for the action of the `HomeController`, which gets a token to call Microsoft Graph:
5353

@@ -57,11 +57,11 @@ public async Task<IActionResult> Profile()
5757
{
5858
// Acquire the access token.
5959
string[] scopes = new string[]{"user.read"};
60-
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
60+
string accessToken = await authorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(scopes);
6161

6262
// Use the access token to call a protected web API.
6363
HttpClient client = new HttpClient();
64-
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
64+
client.DefaultRequestHeaders.Add("Authorization", authorizationHeader);
6565
string json = await client.GetStringAsync(url);
6666
}
6767
```
@@ -82,40 +82,59 @@ These advanced steps are covered in chapter 3 of the [3-WebApp-multi-APIs](https
8282
The code for ASP.NET is similar to the code shown for ASP.NET Core:
8383

8484
- A controller action, protected by an [Authorize] attribute, extracts the tenant ID and user ID of the `ClaimsPrincipal` member of the controller. (ASP.NET uses `HttpContext.User`.)
85-
- From there, it builds an MSAL.NET `IConfidentialClientApplication` object.
86-
- Finally, it calls the `AcquireTokenSilent` method of the confidential client application.
87-
- If interaction is required, the web app needs to challenge the user (re-sign in) and ask for more claims.
85+
*Microsoft.Identity.Web* adds extension methods to the Controller that provide convenience services to call Microsoft Graph or a downstream web API, or to get an authorization header, or even a token. The methods used to call an API directly are explained in detail in [A web app that calls web APIs: Call an API](scenario-web-app-call-api-call-api.md). With these helper methods, you don't need to manually acquire a token.
8886

89-
>[!NOTE]
90-
>The scope should be the fully qualified scope name. For example,`({api_uri}/scope)`.
87+
If, however, you do want to manually acquire a token or build an authorization header, the following code shows how to use *Microsoft.Identity.Web* to do so in a controller. It calls an API (Microsoft Graph) using the REST API instead of the Microsoft Graph SDK.
9188

92-
The following code snippet is extracted from [HomeController.cs#L157-L192](https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect/blob/257c8f96ec3ff875c351d1377b36403eed942a18/WebApp/Controllers/HomeController.cs#L157-L192) in the [ms-identity-aspnet-webapp-openidconnect](https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect) ASP.NET MVC code sample:
89+
To get an authorization header, you get an `IAuthorizationHeaderProvider` service from the controller using an extension method `GetAuthorizationHeaderProvider`. To get an authorization header to call an API on behalf of the user, use `CreateAuthorizationHeaderForUserAsync`. To get an authorization header to call a downstream API on behalf of the application itself, in a daemon scenario, use `CreateAuthorizationHeaderForAppAsync`.
9390

94-
```C#
95-
public async Task<ActionResult> ReadMail()
96-
{
97-
IConfidentialClientApplication app = MsalAppBuilder.BuildConfidentialClientApplication();
98-
AuthenticationResult result = null;
99-
var account = await app.GetAccountAsync(ClaimsPrincipal.Current.GetMsalAccountId());
100-
string[] scopes = { "Mail.Read" };
101-
102-
try
103-
{
104-
// try to get token silently
105-
result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync().ConfigureAwait(false);
106-
}
107-
catch (MsalUiRequiredException)
108-
{
109-
ViewBag.Relogin = "true";
110-
return View();
111-
}
91+
The controller methods are protected by an `[Authorize]` attribute that ensures only authenticated users can use the web app.
92+
93+
94+
The following snippet shows the action of the `HomeController`, which gets an authorization header to call Microsoft Graph as a REST API:
11295

113-
// More code here
114-
return View();
96+
97+
```csharp
98+
[Authorize]
99+
public class HomeController : Controller
100+
{
101+
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
102+
public async Task<IActionResult> Profile()
103+
{
104+
// Get an authorization header.
105+
IAuthorizationHeaderProvider authorizationHeaderProvider = this.GetAuthorizationHeaderProvider();
106+
string[] scopes = new string[]{"user.read"};
107+
string authorizationHeader = await authorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(scopes);
108+
109+
// Use the access token to call a protected web API.
110+
HttpClient client = new HttpClient();
111+
client.DefaultRequestHeaders.Add("Authorization", authorizationHeader);
112+
string json = await client.GetStringAsync(url);
113+
}
115114
}
116115
```
117116

118-
For details see the code for [GetMsalAccountId](https://github.com/Azure-Samples/ms-identity-aspnet-webapp-openidconnect/blob/257c8f96ec3ff875c351d1377b36403eed942a18/WebApp/Utils/ClaimPrincipalExtension.cs#L38) in the code sample.
117+
The following snippet shows the action of the `HomeController`, which gets an access token to call Microsoft Graph as a REST API:
118+
119+
```csharp
120+
[Authorize]
121+
public class HomeController : Controller
122+
{
123+
[AuthorizeForScopes(Scopes = new[] { "user.read" })]
124+
public async Task<IActionResult> Profile()
125+
{
126+
// Get an authorization header.
127+
ITokenAcquirer tokenAcquirer = TokenAcquirerFactory.GetDefaultInstance().GetTokenAcquirer();
128+
string[] scopes = new string[]{"user.read"};
129+
string token = await await tokenAcquirer.GetTokenForUserAsync(scopes);
130+
131+
// Use the access token to call a protected web API.
132+
HttpClient client = new HttpClient();
133+
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {token}");
134+
string json = await client.GetStringAsync(url);
135+
}
136+
}
137+
```
119138

120139

121140
# [Java](#tab/java)

0 commit comments

Comments
 (0)