Skip to content

Commit f94cf7f

Browse files
authored
Update to Microsoft Identity Web 0.3.0-preview. (#159)
* Update to Microsoft Identity Web 0.3.0-preview.
1 parent 9fb500f commit f94cf7f

File tree

14 files changed

+84
-148
lines changed

14 files changed

+84
-148
lines changed

1. Desktop app calls Web API/README-incremental.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,10 @@ Replace:
284284
With:
285285

286286
```CSharp
287-
services.AddMicrosoftWebApiAuthentication(Configuration);
287+
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
288288
```
289289

290-
The method `AddMicrosoftWebApiAuthentication` in Microsoft.Identity.Web ensures that:
290+
The method `AddMicrosoftIdentityWebApiAuthentication` in Microsoft.Identity.Web ensures that:
291291

292292
- the tokens are validated with Microsoft Identity Platform
293293
- the valid audiences are both the ClientID of our Web API (default value of `options.Audience` with the ASP.NET Core template) and api://{ClientID}

1. Desktop app calls Web API/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,10 @@ Replace:
300300
With:
301301

302302
```CSharp
303-
services.AddMicrosoftWebApiAuthentication(Configuration);
303+
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
304304
```
305305

306-
The method `AddMicrosoftWebApiAuthentication` in Microsoft.Identity.Web ensures that:
306+
The method `AddMicrosoftIdentityWebApiAuthentication` in Microsoft.Identity.Web ensures that:
307307

308308
- the tokens are validated with Microsoft Identity Platform
309309
- the valid audiences are both the ClientID of our Web API (default value of `options.Audience` with the ASP.NET Core template) and api://{ClientID}

1. Desktop app calls Web API/TodoListService/Startup.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
45
using Microsoft.AspNetCore.Builder;
56
using Microsoft.AspNetCore.Hosting;
67
using Microsoft.Extensions.Configuration;
@@ -22,7 +23,7 @@ public Startup(IConfiguration configuration)
2223
// This method gets called by the runtime. Use this method to add services to the container.
2324
public void ConfigureServices(IServiceCollection services)
2425
{
25-
services.AddMicrosoftWebApiAuthentication(Configuration);
26+
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
2627

2728
services.AddControllers();
2829
}

1. Desktop app calls Web API/TodoListService/TodoListService.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Microsoft.Identity.Web" Version="0.2.2-preview" />
10+
<PackageReference Include="Microsoft.Identity.Web" Version="0.3.0-preview" />
1111
</ItemGroup>
1212
</Project>

2. Web API now calls Microsoft Graph/README-incremental.md

Lines changed: 13 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -132,60 +132,37 @@ Update `Startup.cs` file:
132132
by
133133

134134
```csharp
135-
services.AddMicrosoftWebApiAuthentication(Configuration)
136-
.AddMicrosoftWebApiCallsWebApi(Configuration)
135+
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
136+
.EnableTokenAcquisitionToCallDownstreamApi()
137137
.AddInMemoryTokenCaches();
138138
```
139-
`AddMicrosoftWebApiAuthentication` subscribes to the `OnTokenValidated` JwtBearerAuthentication event, and in this event, adds the user account into MSAL.NET's user token cache.
139+
140+
`EnableTokenAcquisitionToCallDownstreamApi` subscribes to the `OnTokenValidated` JwtBearerAuthentication event, and in this event, adds the user account into MSAL.NET's user token cache.
140141

141142
`AddInMemoryTokenCaches` adds an in memory token cache provider, which will cache the Access Tokens acquired for the downstream Web API.
142143
#### Modify the TodoListController.cs file to add information on the todo item about its owner
143144

144-
In the `TodoListController.cs` file, the Post() action was modified.
145+
In the `TodoListController.cs` file, the `Post` method is modified by replacing
146+
145147
```CSharp
146148
todoStore.Add(new TodoItem { Owner = owner, Title = Todo.Title });
147149
```
148150

149-
is replaced by:
151+
with
150152

151153
```CSharp
152-
ownerName = await CallGraphAPIOnBehalfOfUser();
153-
string title = string.IsNullOrWhiteSpace(ownerName) ? Todo.Title : $"{Todo.Title} ({ownerName})";
154-
todoStore.Add(new TodoItem { Owner = owner, Title = title });
154+
User user = _graphServiceClient.Me.Request().GetAsync().GetAwaiter().GetResult();
155+
string title = string.IsNullOrWhiteSpace(user.UserPrincipalName) ? todo.Title : $"{todo.Title} ({user.UserPrincipalName})";
156+
TodoStore.Add(new TodoItem { Owner = owner, Title = title });
155157
```
156158

157-
The work of calling the Microsoft Graph to get the owner name is done in `CallGraphAPIOnBehalfOfUser()`.
159+
The work of calling Microsoft Graph to get the owner name is done by `GraphServiceClient`, which is set up by Microsoft Identity Web.
158160

159-
This method:
161+
`GraphServiceClient`
160162

161-
- gets an Access Token for the Microsoft Graph on behalf of the user (leveraging the in memory token cache, which was added on the `Startup.cs`)
163+
- gets an access token for the Microsoft Graph on behalf of the user (leveraging the in-memory token cache, which was added in the `Startup.cs`), and
162164
- calls the Microsoft Graph `/me` endpoint to retrieve the name of the user.
163165

164-
```CSharp
165-
public async Task<string> CallGraphAPIOnBehalfOfUser()
166-
{
167-
string[] scopes = new string[] { "user.read" };
168-
169-
// we use MSAL.NET to get a token to call the API On Behalf Of the current user
170-
try
171-
{
172-
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
173-
dynamic me = await CallGraphApiOnBehalfOfUser(accessToken);
174-
return me.userPrincipalName;
175-
}
176-
catch (MicrosoftIdentityWebChallengeUserException ex)
177-
{
178-
await tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopes, ex.MsalUiRequiredException);
179-
return string.Empty;
180-
}
181-
catch (MsalUiRequiredException ex)
182-
{
183-
await tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopes, ex);
184-
return string.Empty;
185-
}
186-
}
187-
```
188-
189166
### Handling required interactions with the user (dynamic consent, MFA, etc.)
190167

191168
Please refer to [Microsoft.Identity.Web\wiki](https://github.com/AzureAD/microsoft-identity-web/wiki/web-apis) on how Web APIs should handle exceptions which require user interaction like `MFA`.

2. Web API now calls Microsoft Graph/README.md

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -286,12 +286,12 @@ Update `Startup.cs` file:
286286
by
287287

288288
```csharp
289-
services.AddMicrosoftWebApiAuthentication(Configuration)
290-
.AddMicrosoftWebApiCallsWebApi(Configuration)
289+
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
290+
.EnableTokenAcquisitionToCallDownstreamApi()
291291
.AddInMemoryTokenCaches();
292292
```
293293

294-
`AddMicrosoftWebApiAuthentication` does the following:
294+
`AddMicrosoftIdentityWebApiAuthentication` does the following:
295295
- add the **JwtBearerAuthenticationScheme** (Note the replacement of BearerAuthenticationScheme by JwtBearerAuthenticationScheme)
296296
- set the authority to be the Microsoft identity platform identity
297297
- set the audiences to be validated
@@ -304,57 +304,33 @@ Update `Startup.cs` file:
304304

305305
The implementations of these classes are in the [Microsoft.Identity.Web](https://github.com/AzureAD/microsoft-identity-web) library, and they are designed to be reusable in your applications (Web apps and Web apis).
306306

307-
`AddMicrosoftWebApiCallsWebApi` subscribes to the `OnTokenValidated` JwtBearerAuthentication event, and in this event, adds the user account into MSAL.NET's user token cache.
307+
`EnableTokenAcquisitionToCallDownstreamApi` subscribes to the `OnTokenValidated` JwtBearerAuthentication event, and in this event, adds the user account into MSAL.NET's user token cache.
308308

309309
`AddInMemoryTokenCaches` adds an in memory token cache provider, which will cache the Access Tokens acquired for the downstream Web API.
310310

311311
### Modify the TodoListController.cs file to add information on the todo item about its owner
312312

313-
In the `TodoListController.cs` file, the Post() action was modified.
313+
In the `TodoListController.cs` file, the `Post` method is modified by replacing
314314

315315
```CSharp
316316
todoStore.Add(new TodoItem { Owner = owner, Title = Todo.Title });
317317
```
318318

319-
is replaced by:
319+
with
320320

321321
```CSharp
322-
ownerName = await CallGraphAPIOnBehalfOfUser();
323-
string title = string.IsNullOrWhiteSpace(ownerName) ? Todo.Title : $"{Todo.Title} ({ownerName})";
324-
todoStore.Add(new TodoItem { Owner = owner, Title = title });
322+
User user = _graphServiceClient.Me.Request().GetAsync().GetAwaiter().GetResult();
323+
string title = string.IsNullOrWhiteSpace(user.UserPrincipalName) ? todo.Title : $"{todo.Title} ({user.UserPrincipalName})";
324+
TodoStore.Add(new TodoItem { Owner = owner, Title = title });
325325
```
326326

327-
The work of calling the Microsoft Graph to get the owner name is done in `CallGraphAPIOnBehalfOfUser()`.
327+
The work of calling Microsoft Graph to get the owner name is done by `GraphServiceClient`, which is set up by Microsoft Identity Web.
328328

329-
This method:
329+
`GraphServiceClient`
330330

331-
- gets an Access Token for the Microsoft Graph on behalf of the user (leveraging the in memory token cache, which was added on the `Startup.cs`)
331+
- gets an access token for the Microsoft Graph on behalf of the user (leveraging the in-memory token cache, which was added in the `Startup.cs`), and
332332
- calls the Microsoft Graph `/me` endpoint to retrieve the name of the user.
333333

334-
```CSharp
335-
public async Task<string> CallGraphAPIOnBehalfOfUser()
336-
{
337-
string[] scopes = new string[] { "user.read" };
338-
339-
// we use MSAL.NET to get a token to call the API On Behalf Of the current user
340-
try
341-
{
342-
string accessToken = await tokenAcquisition.GetAccessTokenForUserAsync(scopes);
343-
dynamic me = await CallGraphApiOnBehalfOfUser(accessToken);
344-
return me.userPrincipalName;
345-
}
346-
catch (MicrosoftIdentityWebChallengeUserException ex)
347-
{
348-
await tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopes, ex.MsalUiRequiredException);
349-
return string.Empty;
350-
}
351-
catch (MsalUiRequiredException ex)
352-
{
353-
await tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopes, ex);
354-
return string.Empty;
355-
}
356-
}
357-
```
358334

359335
### Handling required interactions with the user (dynamic consent, MFA, etc.)
360336

2. Web API now calls Microsoft Graph/TodoListService/Controllers/TodoListController.cs

Lines changed: 27 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,20 @@
55
// In the first chapter this is just a protected API (ENABLE_OBO is not set)
66
// In this chapter, the Web API calls a downstream API on behalf of the user (OBO)
77
#define ENABLE_OBO
8+
using System;
9+
using System.Collections.Concurrent;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
using System.Net;
13+
using System.Security.Claims;
814
using Microsoft.AspNetCore.Authorization;
915
using Microsoft.AspNetCore.Http;
1016
using Microsoft.AspNetCore.Mvc;
17+
using Microsoft.Extensions.Options;
1118
using Microsoft.Graph;
1219
using Microsoft.Identity.Client;
1320
using Microsoft.Identity.Web;
1421
using Microsoft.Identity.Web.Resource;
15-
using Newtonsoft.Json;
16-
using System;
17-
using System.Collections.Concurrent;
18-
using System.Collections.Generic;
19-
using System.Linq;
20-
using System.Net;
21-
using System.Net.Http;
22-
using System.Net.Http.Headers;
23-
using System.Security.Claims;
24-
using System.Threading.Tasks;
2522
using TodoListService.Models;
2623

2724
namespace TodoListService.Controllers
@@ -30,12 +27,16 @@ namespace TodoListService.Controllers
3027
[Route("api/[controller]")]
3128
public class TodoListController : Controller
3229
{
33-
public TodoListController(ITokenAcquisition tokenAcquisition)
30+
public TodoListController(ITokenAcquisition tokenAcquisition, GraphServiceClient graphServiceClient, IOptions<MicrosoftGraphOptions> graphOptions)
3431
{
3532
_tokenAcquisition = tokenAcquisition;
33+
_graphServiceClient = graphServiceClient;
34+
_graphOptions = graphOptions;
3635
}
3736

38-
readonly ITokenAcquisition _tokenAcquisition;
37+
private readonly ITokenAcquisition _tokenAcquisition;
38+
private readonly GraphServiceClient _graphServiceClient;
39+
private readonly IOptions<MicrosoftGraphOptions> _graphOptions;
3940

4041
static readonly ConcurrentBag<TodoItem> TodoStore = new ConcurrentBag<TodoItem>();
4142

@@ -56,18 +57,17 @@ public IEnumerable<TodoItem> Get()
5657

5758
// POST api/values
5859
[HttpPost]
59-
public async void Post([FromBody]TodoItem todo)
60+
public async void Post([FromBody] TodoItem todo)
6061
{
6162
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
6263
string owner = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
63-
string ownerName;
6464
#if ENABLE_OBO
6565
// This is a synchronous call, so that the clients know, when they call Get, that the
6666
// call to the downstream API (Microsoft Graph) has completed.
6767
try
6868
{
69-
ownerName = CallGraphApiOnBehalfOfUser().GetAwaiter().GetResult();
70-
string title = string.IsNullOrWhiteSpace(ownerName) ? todo.Title : $"{todo.Title} ({ownerName})";
69+
User user = _graphServiceClient.Me.Request().GetAsync().GetAwaiter().GetResult();
70+
string title = string.IsNullOrWhiteSpace(user.UserPrincipalName) ? todo.Title : $"{todo.Title} ({user.UserPrincipalName})";
7171
TodoStore.Add(new TodoItem { Owner = owner, Title = title });
7272
}
7373
catch (MsalException ex)
@@ -78,48 +78,19 @@ public async void Post([FromBody]TodoItem todo)
7878
}
7979
catch (Exception ex)
8080
{
81-
HttpContext.Response.ContentType = "text/plain";
82-
HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
83-
await HttpContext.Response.WriteAsync("An error occurred while calling the downstream API\n" + ex.Message);
81+
if (ex.InnerException is MicrosoftIdentityWebChallengeUserException challengeException)
82+
{
83+
await _tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(_graphOptions.Value.Scopes.Split(' '),
84+
challengeException.MsalUiRequiredException);
85+
}
86+
else
87+
{
88+
HttpContext.Response.ContentType = "text/plain";
89+
HttpContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
90+
await HttpContext.Response.WriteAsync("An error occurred while calling the downstream API\n" + ex.Message);
91+
}
8492
}
8593
#endif
86-
87-
}
88-
89-
public async Task<string> CallGraphApiOnBehalfOfUser()
90-
{
91-
string[] scopes = { "user.read" };
92-
93-
// we use MSAL.NET to get a token to call the API On Behalf Of the current user
94-
try
95-
{
96-
string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes);
97-
dynamic me = await CallGraphApiOnBehalfOfUser(accessToken);
98-
return me.UserPrincipalName;
99-
}
100-
catch (MicrosoftIdentityWebChallengeUserException ex)
101-
{
102-
await _tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopes, ex.MsalUiRequiredException);
103-
return string.Empty;
104-
}
105-
catch (MsalUiRequiredException ex)
106-
{
107-
await _tokenAcquisition.ReplyForbiddenWithWwwAuthenticateHeaderAsync(scopes, ex);
108-
return string.Empty;
109-
}
110-
}
111-
112-
private static async Task<dynamic> CallGraphApiOnBehalfOfUser(string accessToken)
113-
{
114-
// Call the Graph API and retrieve the user's profile.
115-
GraphServiceClient graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider((requestMessage) => {
116-
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
117-
return Task.FromResult(0);
118-
}));
119-
120-
User graphUser = await graphServiceClient.Me.Request().GetAsync();
121-
122-
return graphUser;
12394
}
12495
}
12596
}

2. Web API now calls Microsoft Graph/TodoListService/Startup.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
using Microsoft.Extensions.DependencyInjection;
88
using Microsoft.Extensions.Hosting;
99
using Microsoft.Identity.Web;
10-
using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
1110

1211
namespace TodoListService
1312
{
@@ -23,9 +22,11 @@ public Startup(IConfiguration configuration)
2322
// This method gets called by the runtime. Use this method to add services to the container.
2423
public void ConfigureServices(IServiceCollection services)
2524
{
26-
services.AddMicrosoftWebApiAuthentication(Configuration)
27-
.AddMicrosoftWebApiCallsWebApi(Configuration)
28-
.AddInMemoryTokenCaches();
25+
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
26+
.EnableTokenAcquisitionToCallDownstreamApi()
27+
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
28+
.AddInMemoryTokenCaches();
29+
2930
services.AddControllers();
3031
}
3132

2. Web API now calls Microsoft Graph/TodoListService/TodoListService.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
</ItemGroup>
1212

1313
<ItemGroup>
14-
<PackageReference Include="Microsoft.Identity.Web" Version="0.2.2-preview" />
15-
<PackageReference Include="Microsoft.Graph" Version="3.8.0" />
14+
<PackageReference Include="Microsoft.Identity.Web" Version="0.3.0-preview" />
1615
</ItemGroup>
1716

1817
</Project>

2. Web API now calls Microsoft Graph/TodoListService/appsettings.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"AzureAd": {
33
"Instance": "https://login.microsoftonline.com/",
44
"ClientId": "[Enter_client_ID_Of_TodoListService-v2_from_Azure_Portal,_e.g._2ec40e65-ba09-4853-bcde-bcb60029e596]",
5-
"ClientSecret": "[Enter_client_secret_as_added_fom_the_certificates_&_secrets_page_from_your_app_registration]",
5+
"ClientSecret": "[Enter_client_secret_as_added_fom_the_certificates_&_secrets_page_from_your_app_registration]",
66

77
/*
88
You need specify the TenantId only if you want to accept access tokens from a single tenant (line of business app)
@@ -12,6 +12,17 @@
1212
"TenantId": "common" // A guid (Tenant ID = Directory ID) or 'common' or 'organizations' or 'consumers'
1313

1414
},
15+
"DownstreamAPI": {
16+
/*
17+
'Scopes' contains space separated scopes of the web API you want to call. This can be:
18+
- a scope for a V2 application (for instance api://b3682cc7-8b30-4bd2-aaba-080c6bf0fd31/access_as_user)
19+
- a scope corresponding to a V1 application (for instance <App ID URI>/.default, where <App ID URI> is the
20+
App ID URI of a legacy v1 web application
21+
Applications are registered in the https://portal.azure.com portal.
22+
*/
23+
"BaseUrl": "[WebApiUrl]",
24+
"Scopes": "user.read"
25+
},
1526
"Kestrel": {
1627
"Endpoints": {
1728
"Http": {

0 commit comments

Comments
 (0)