Skip to content

Commit ade6ad3

Browse files
authored
Blazor WASM security topic updates (#18133)
1 parent f283401 commit ade6ad3

4 files changed

Lines changed: 155 additions & 123 deletions

File tree

aspnetcore/blazor/call-web-api.md

Lines changed: 2 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -178,90 +178,10 @@ The [Blazor WebAssembly sample app (BlazorWebAssemblySample)](https://github.com
178178

179179
To allow other sites to make cross-origin resource sharing (CORS) requests to your app, see <xref:security/cors>.
180180

181-
## HttpClient and HttpRequestMessage with Fetch API request options
182-
183-
When running on WebAssembly in a Blazor WebAssembly app, use [HttpClient](xref:fundamentals/http-requests) and <xref:System.Net.Http.HttpRequestMessage> to customize requests. For example, you can specify the request URI, HTTP method, and any desired request headers.
184-
185-
```razor
186-
@using System.Net.Http
187-
@using System.Net.Http.Headers
188-
@using System.Net.Http.Json
189-
@inject HttpClient Http
190-
191-
@code {
192-
private async Task PostRequest()
193-
{
194-
var requestMessage = new HttpRequestMessage()
195-
{
196-
Method = new HttpMethod("POST"),
197-
RequestUri = new Uri("https://localhost:10000/api/TodoItems"),
198-
Content =
199-
JsonContent.Create(new TodoItem
200-
{
201-
Name: "A New Todo Item",
202-
IsComplete: false
203-
})
204-
};
205-
206-
requestMessage.Headers.Authorization =
207-
new AuthenticationHeaderValue("Bearer", "{OAUTH TOKEN}");
208-
209-
requestMessage.Content.Headers.TryAddWithoutValidation(
210-
"x-custom-header", "value");
211-
212-
var response = await Http.SendAsync(requestMessage);
213-
var responseStatusCode = response.StatusCode;
214-
var responseBody = await response.Content.ReadAsStringAsync();
215-
}
216-
}
217-
```
218-
219-
.NET WebAssembly's implementation of `HttpClient` uses [WindowOrWorkerGlobalScope.fetch()](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch). Fetch allows configuring several [request-specific options](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters).
220-
221-
HTTP fetch request options can be configured with `HttpRequestMessage` extension methods shown in the following table.
222-
223-
| `HttpRequestMessage` extension method | Fetch request property |
224-
| ------------------------------------- | ---------------------- |
225-
| `SetBrowserRequestCredentials` | [credentials](https://developer.mozilla.org/docs/Web/API/Request/credentials) |
226-
| `SetBrowserRequestCache` | [cache](https://developer.mozilla.org/docs/Web/API/Request/cache) |
227-
| `SetBrowserRequestMode` | [mode](https://developer.mozilla.org/docs/Web/API/Request/mode) |
228-
| `SetBrowserRequestIntegrity` | [integrity](https://developer.mozilla.org/docs/Web/API/Request/integrity) |
229-
230-
You can set additional options using the more generic `SetBrowserRequestOption` extension method.
231-
232-
The HTTP response is typically buffered in a Blazor WebAssembly app to enable support for sync reads on the response content. To enable support for response streaming, use the `SetBrowserResponseStreamingEnabled` extension method on the request.
233-
234-
To include credentials in a cross-origin request, use the `SetBrowserRequestCredentials` extension method:
235-
236-
```csharp
237-
requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
238-
```
239-
240-
For more information on Fetch API options, see [MDN web docs: WindowOrWorkerGlobalScope.fetch():Parameters](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters).
241-
242-
When sending credentials (authorization cookies/headers) on CORS requests, the `Authorization` header must be allowed by the CORS policy.
243-
244-
The following policy includes configuration for:
245-
246-
* Request origins (`http://localhost:5000`, `https://localhost:5001`).
247-
* Any method (verb).
248-
* `Content-Type` and `Authorization` headers. To allow a custom header (for example, `x-custom-header`), list the header when calling <xref:Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder.WithHeaders*>.
249-
* Credentials set by client-side JavaScript code (`credentials` property set to `include`).
250-
251-
```csharp
252-
app.UseCors(policy =>
253-
policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
254-
.AllowAnyMethod()
255-
.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header")
256-
.AllowCredentials());
257-
```
258-
259-
For more information, see <xref:security/cors> and the sample app's HTTP Request Tester component (*Components/HTTPRequestTester.razor*).
260-
261181
## Additional resources
262182

263-
* [Request additional access tokens](xref:security/blazor/webassembly/additional-scenarios#request-additional-access-tokens)
264-
* [Attach tokens to outgoing requests](xref:security/blazor/webassembly/additional-scenarios#attach-tokens-to-outgoing-requests)
183+
* <xref:security/blazor/webassembly/index>
184+
* <xref:security/blazor/webassembly/additional-scenarios>
265185
* <xref:fundamentals/http-requests>
266186
* <xref:security/enforcing-ssl>
267187
* [Kestrel HTTPS endpoint configuration](xref:fundamentals/servers/kestrel#endpoint-configuration)

aspnetcore/includes/blazor-security/fetchdata-component.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ The `FetchData` component shows how to:
55

66
The `@attribute [Authorize]` directive indicates to the Blazor WebAssembly authorization system that the user must be authorized in order to visit this component. The presence of the attribute in the *Client* app doesn't prevent the API on the server from being called without proper credentials. The *Server* app also must use `[Authorize]` on the appropriate endpoints to correctly protect them.
77

8-
`AuthenticationService.RequestAccessToken();` takes care of requesting an access token that can be added to the request to call the API. If the token is cached or the service is able to provision a new access token without user interaction, the token request succeeds. Otherwise, the token request fails.
8+
`IAccessTokenProvider.RequestAccessToken();` takes care of requesting an access token that can be added to the request to call the API. If the token is cached or the service is able to provision a new access token without user interaction, the token request succeeds. Otherwise, the token request fails with an `AccessTokenNotAvailableException` error, which is caught in a `try-catch` statement.
99

1010
In order to obtain the actual token to include in the request, the app must check that the request succeeded by calling `tokenResult.TryGetToken(out var token)`.
1111

aspnetcore/security/blazor/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ description: Learn about Blazor authentication and authorization scenarios.
55
monikerRange: '>= aspnetcore-3.1'
66
ms.author: riande
77
ms.custom: mvc
8-
ms.date: 03/26/2020
8+
ms.date: 05/04/2020
99
no-loc: [Blazor, "Identity", "Let's Encrypt", Razor, SignalR]
1010
uid: security/blazor/index
1111
---
@@ -418,6 +418,7 @@ If the app determines that the underlying authentication state data has changed
418418
If the app is required to check authorization rules as part of procedural logic, use a cascaded parameter of type `Task<AuthenticationState>` to obtain the user's <xref:System.Security.Claims.ClaimsPrincipal>. `Task<AuthenticationState>` can be combined with other services, such as `IAuthorizationService`, to evaluate policies.
419419

420420
```razor
421+
@using Microsoft.AspNetCore.Authorization
421422
@inject IAuthorizationService AuthorizationService
422423
423424
<button @onclick="@DoSomething">Do something important</button>

aspnetcore/security/blazor/webassembly/additional-scenarios.md

Lines changed: 150 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,6 @@ By [Javier Calvarro Nelson](https://github.com/javiercn)
1717

1818
[!INCLUDE[](~/includes/blazorwasm-3.2-template-article-notice.md)]
1919

20-
## Request additional access tokens
21-
22-
Most apps only require an access token to interact with the protected resources that they use. In some scenarios, an app might require more than one token in order to interact with two or more resources.
23-
24-
In the following example, additional Azure Active Directory (AAD) Microsoft Graph API scopes are required by an app to read user data and send mail. After adding the Microsoft Graph API permissions in the Azure AAD portal, the additional scopes are configured in the Client app (`Program.Main`, *Program.cs*):
25-
26-
```csharp
27-
builder.Services.AddMsalAuthentication(options =>
28-
{
29-
...
30-
31-
options.ProviderOptions.AdditionalScopesToConsent.Add(
32-
"https://graph.microsoft.com/Mail.Send");
33-
options.ProviderOptions.AdditionalScopesToConsent.Add(
34-
"https://graph.microsoft.com/User.Read");
35-
}
36-
```
37-
38-
The `IAccessTokenProvider.RequestToken` method provides an overload that allows an app to provision an access token with a given set of scopes, as seen in the following example:
39-
40-
```csharp
41-
var tokenResult = await AuthenticationService.RequestAccessToken(
42-
new AccessTokenRequestOptions
43-
{
44-
Scopes = new[] { "https://graph.microsoft.com/Mail.Send",
45-
"https://graph.microsoft.com/User.Read" }
46-
});
47-
48-
if (tokenResult.TryGetToken(out var token))
49-
{
50-
...
51-
}
52-
```
53-
54-
`TryGetToken` returns:
55-
56-
* `true` with the `token` for use.
57-
* `false` if the token isn't retrieved.
58-
5920
## Attach tokens to outgoing requests
6021

6122
The `AuthorizationMessageHandler` service can be used with `HttpClient` to attach access tokens to outgoing requests. Tokens are acquired using the existing `IAccessTokenProvider` service. If a token can't be acquired, an `AccessTokenNotAvailableException` is thrown. `AccessTokenNotAvailableException` has a `Redirect` method that can be used to navigate the user to the identity provider to acquire a new token. The `AuthorizationMessageHandler` can be configured with the authorized URLs, scopes, and return URL using the `ConfigureHandler` method.
@@ -159,6 +120,156 @@ protected override async Task OnInitializedAsync()
159120
}
160121
```
161122

123+
## Request additional access tokens
124+
125+
Access tokens can be manually obtained by calling `IAccessTokenProvider.RequestAccessToken`.
126+
127+
In the following example, additional Azure Active Directory (AAD) Microsoft Graph API scopes are required by an app to read user data and send mail. After adding the Microsoft Graph API permissions in the Azure AAD portal, the additional scopes are configured in the Client app (`Program.Main`, *Program.cs*):
128+
129+
```csharp
130+
builder.Services.AddMsalAuthentication(options =>
131+
{
132+
...
133+
134+
options.ProviderOptions.AdditionalScopesToConsent.Add(
135+
"https://graph.microsoft.com/Mail.Send");
136+
options.ProviderOptions.AdditionalScopesToConsent.Add(
137+
"https://graph.microsoft.com/User.Read");
138+
}
139+
```
140+
141+
The `IAccessTokenProvider.RequestToken` method provides an overload that allows an app to provision an access token with a given set of scopes, as seen in the following example:
142+
143+
```csharp
144+
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
145+
@inject IAccessTokenProvider TokenProvider
146+
147+
...
148+
149+
var tokenResult = await TokenProvider.RequestAccessToken(
150+
new AccessTokenRequestOptions
151+
{
152+
Scopes = new[] { "https://graph.microsoft.com/Mail.Send",
153+
"https://graph.microsoft.com/User.Read" }
154+
});
155+
156+
if (tokenResult.TryGetToken(out var token))
157+
{
158+
...
159+
}
160+
```
161+
162+
`TryGetToken` returns:
163+
164+
* `true` with the `token` for use.
165+
* `false` if the token isn't retrieved.
166+
167+
## HttpClient and HttpRequestMessage with Fetch API request options
168+
169+
When running on WebAssembly in a Blazor WebAssembly app, [HttpClient](xref:fundamentals/http-requests) and <xref:System.Net.Http.HttpRequestMessage> can be used to customize requests. For example, you can specify the HTTP method and request headers. The following example makes a `POST` request to a To Do List API endpoint on the server and shows the response body:
170+
171+
```razor
172+
@page "/todorequest"
173+
@using System.Net.Http
174+
@using System.Net.Http.Headers
175+
@using System.Net.Http.Json
176+
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
177+
@inject HttpClient Http
178+
@inject IAccessTokenProvider TokenProvider
179+
180+
<h1>ToDo Request</h1>
181+
182+
<button @onclick="PostRequest">Submit POST request</button>
183+
184+
<p>Response body returned by the server:</p>
185+
186+
<p>@_responseBody</p>
187+
188+
@code {
189+
private string _responseBody;
190+
191+
private async Task PostRequest()
192+
{
193+
var requestMessage = new HttpRequestMessage()
194+
{
195+
Method = new HttpMethod("POST"),
196+
RequestUri = new Uri("https://localhost:10000/api/TodoItems"),
197+
Content =
198+
JsonContent.Create(new TodoItem
199+
{
200+
Name = "My New Todo Item",
201+
IsComplete = false
202+
})
203+
};
204+
205+
var tokenResult = await TokenProvider.RequestAccessToken();
206+
207+
if (tokenResult.TryGetToken(out var token))
208+
{
209+
requestMessage.Headers.Authorization =
210+
new AuthenticationHeaderValue("Bearer", token.Value);
211+
212+
requestMessage.Content.Headers.TryAddWithoutValidation(
213+
"x-custom-header", "value");
214+
215+
var response = await Http.SendAsync(requestMessage);
216+
var responseStatusCode = response.StatusCode;
217+
218+
_responseBody = await response.Content.ReadAsStringAsync();
219+
}
220+
}
221+
222+
public class TodoItem
223+
{
224+
public long Id { get; set; }
225+
public string Name { get; set; }
226+
public bool IsComplete { get; set; }
227+
}
228+
}
229+
```
230+
231+
.NET WebAssembly's implementation of `HttpClient` uses [WindowOrWorkerGlobalScope.fetch()](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch). Fetch allows configuring several [request-specific options](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters).
232+
233+
HTTP fetch request options can be configured with `HttpRequestMessage` extension methods shown in the following table.
234+
235+
| `HttpRequestMessage` extension method | Fetch request property |
236+
| ------------------------------------- | ---------------------- |
237+
| `SetBrowserRequestCredentials` | [credentials](https://developer.mozilla.org/docs/Web/API/Request/credentials) |
238+
| `SetBrowserRequestCache` | [cache](https://developer.mozilla.org/docs/Web/API/Request/cache) |
239+
| `SetBrowserRequestMode` | [mode](https://developer.mozilla.org/docs/Web/API/Request/mode) |
240+
| `SetBrowserRequestIntegrity` | [integrity](https://developer.mozilla.org/docs/Web/API/Request/integrity) |
241+
242+
You can set additional options using the more generic `SetBrowserRequestOption` extension method.
243+
244+
The HTTP response is typically buffered in a Blazor WebAssembly app to enable support for sync reads on the response content. To enable support for response streaming, use the `SetBrowserResponseStreamingEnabled` extension method on the request.
245+
246+
To include credentials in a cross-origin request, use the `SetBrowserRequestCredentials` extension method:
247+
248+
```csharp
249+
requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
250+
```
251+
252+
For more information on Fetch API options, see [MDN web docs: WindowOrWorkerGlobalScope.fetch():Parameters](https://developer.mozilla.org/docs/Web/API/WindowOrWorkerGlobalScope/fetch#Parameters).
253+
254+
When sending credentials (authorization cookies/headers) on CORS requests, the `Authorization` header must be allowed by the CORS policy.
255+
256+
The following policy includes configuration for:
257+
258+
* Request origins (`http://localhost:5000`, `https://localhost:5001`).
259+
* Any method (verb).
260+
* `Content-Type` and `Authorization` headers. To allow a custom header (for example, `x-custom-header`), list the header when calling <xref:Microsoft.AspNetCore.Cors.Infrastructure.CorsPolicyBuilder.WithHeaders*>.
261+
* Credentials set by client-side JavaScript code (`credentials` property set to `include`).
262+
263+
```csharp
264+
app.UseCors(policy =>
265+
policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
266+
.AllowAnyMethod()
267+
.WithHeaders(HeaderNames.ContentType, HeaderNames.Authorization, "x-custom-header")
268+
.AllowCredentials());
269+
```
270+
271+
For more information, see <xref:security/cors> and the sample app's HTTP Request Tester component (*Components/HTTPRequestTester.razor*).
272+
162273
## Handle token request errors
163274

164275
When a Single Page Application (SPA) authenticates a user using Open ID Connect (OIDC), the authentication state is maintained locally within the SPA and in the Identity Provider (IP) in the form of a session cookie that's set as a result of the user providing their credentials.

0 commit comments

Comments
 (0)