Skip to content

Commit 51a0674

Browse files
authored
Secure data with Interactive Auto rendering (#34765)
1 parent 5601ac1 commit 51a0674

5 files changed

Lines changed: 234 additions & 2 deletions

File tree

aspnetcore/blazor/call-web-api.md

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ The [`System.Net.Http.Json`](https://www.nuget.org/packages/System.Net.Http.Json
2222

2323
## Sample apps
2424

25-
See the sample apps in the [`dotnet/blazor-samples`](https://github.com/dotnet/blazor-samples/) GitHub repository.
25+
For working examples, see the following sample apps in the [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples/) ([how to download](xref:blazor/fundamentals/index#sample-apps)).
2626

2727
### `BlazorWebAppCallWebApi`
2828

@@ -80,6 +80,8 @@ public class ServerMovieService(MovieContext db) : IMovieService
8080
}
8181
```
8282

83+
For more information on how to secure movie data in this scenario, see the weather data example described by [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering).
84+
8385
### `BlazorWebAppCallWebApi_Weather`
8486

8587
A weather data sample app that uses streaming rendering for weather data.
@@ -91,6 +93,40 @@ Calls a todo list web API from a Blazor WebAssembly app:
9193
* `Backend`: A web API app for maintaining a todo list, based on [Minimal APIs](xref:fundamentals/minimal-apis).
9294
* `BlazorTodo`: A Blazor WebAssembly app that calls the web API with a preconfigured <xref:System.Net.Http.HttpClient> for todo list CRUD operations.
9395

96+
### `BlazorWebAssemblyStandaloneWithIdentity`
97+
98+
A standalone Blazor WebAssembly app secured with ASP.NET Core Identity:
99+
100+
* `Backend`: A backend web API app that maintains a user identity store for ASP.NET Core Identity.
101+
* `BlazorWasmAuth`: A standalone Blazor WebAssembly frontend app with user authentication.
102+
103+
The solution demonstrates calling a secure web API for the following:
104+
105+
* Obtaining an authenticated user's roles.
106+
* Data processing for all authenticated users.
107+
* Data processing for authorized users (the user must be in the `Manager` role) via an [authorization policy](xref:security/authorization/policies).
108+
109+
### `BlazorWebAppOidc`
110+
111+
A Blazor Web App with global Auto interactivity that uses OIDC authentication with Microsoft Entra without using Entra-specific packages. The solution includes a demonstration of obtaining weather data securely via a web API when a component that adopts Interactive Auto rendering is rendered on the client.
112+
113+
### `BlazorWebAppOidcBff`
114+
115+
A Blazor Web App with global Auto interactivity that uses:
116+
117+
* OIDC authentication with Microsoft Entra without using Entra-specific packages.
118+
* The [Backend for Frontend (BFF) pattern](/azure/architecture/patterns/backends-for-frontends), which is a pattern of app development that creates backend services for frontend apps or interfaces.
119+
120+
The solution includes a demonstration of obtaining weather data securely via a web API when a component that adopts Interactive Auto rendering is rendered on the client.
121+
122+
:::moniker-end
123+
124+
:::moniker range=">= aspnetcore-9.0"
125+
126+
### `BlazorWebAppEntra`
127+
128+
A Blazor Web App with global Auto interactivity that uses [Microsoft identity platform](/entra/identity-platform/)/[Microsoft Identity Web packages](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra). The solution includes a demonstration of obtaining weather data securely via a web API when a component that adopts Interactive Auto rendering is rendered on the client.
129+
94130
:::moniker-end
95131

96132
## Client-side scenarios for calling external web APIs
@@ -1050,9 +1086,21 @@ Various network tools are publicly available for testing web API backend apps di
10501086

10511087
### General
10521088

1089+
:::moniker range=">= aspnetcore-8.0"
1090+
1091+
* [Cross-Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/)
1092+
* <xref:security/cors>: Although the content applies to ASP.NET Core apps, not Razor components, the article covers general CORS concepts.
1093+
* [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering)
1094+
1095+
:::moniker-end
1096+
1097+
:::moniker range="< aspnetcore-8.0"
1098+
10531099
* [Cross-Origin Resource Sharing (CORS) at W3C](https://www.w3.org/TR/cors/)
10541100
* <xref:security/cors>: Although the content applies to ASP.NET Core apps, not Razor components, the article covers general CORS concepts.
10551101

1102+
:::moniker-end
1103+
10561104
### Mitigation of overposting attacks
10571105

10581106
Web APIs can be vulnerable to an *overposting* attack, also known as a *mass assignment* attack. An overposting attack occurs when a malicious user issues an HTML form POST to the server that processes data for properties that aren't part of the rendered form and that the developer doesn't wish to allow users to modify. The term "overposting" literally means that the malicious user has *over*-POSTed with the form.

aspnetcore/blazor/components/render-modes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ To address this scenario, inject the service in a new imports file placed in the
971971
* <xref:blazor/js-interop/ssr>
972972
* [Cascading values/parameters and render mode boundaries](xref:blazor/components/cascading-values-and-parameters#cascading-valuesparameters-and-render-mode-boundaries): Also see the [Root-level cascading parameters](xref:blazor/components/cascading-values-and-parameters#root-level-cascading-parameters) section earlier in the article.
973973
* <xref:blazor/components/class-libraries-with-static-ssr>
974+
* [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering)
974975

975976
:::moniker-end
976977

@@ -979,5 +980,6 @@ To address this scenario, inject the service in a new imports file placed in the
979980
* <xref:blazor/js-interop/ssr>
980981
* [Cascading values/parameters and render mode boundaries](xref:blazor/components/cascading-values-and-parameters#cascading-valuesparameters-and-render-mode-boundaries): Also see the [Root-level cascading parameters](xref:blazor/components/cascading-values-and-parameters#root-level-cascading-parameters) section earlier in the article.
981982
* <xref:blazor/components/class-libraries-with-static-ssr>
983+
* [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering)
982984

983985
:::moniker-end

aspnetcore/blazor/security/blazor-web-app-with-entra.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ Alternatively, use the following `LogInOrOut` component, which doesn't supply a
263263
</div>
264264
```
265265

266+
## Weather data security
267+
268+
For more information on how this app secures its weather data, see [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering).
269+
266270
## Troubleshoot
267271

268272
[!INCLUDE[](~/blazor/security/includes/troubleshoot-server.md)]

aspnetcore/blazor/security/blazor-web-app-with-oidc.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,7 @@ The following <xref:Microsoft.AspNetCore.Authentication.OpenIdConnect.OpenIdConn
430430
Inspect the sample app for the following features:
431431

432432
* Automatic non-interactive token refresh with the help of a custom cookie refresher (`CookieOidcRefresher.cs`).
433-
* The `Weather` component uses the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to prevent unauthorized access. For more information on requiring authorization across the app via an [authorization policy](xref:security/authorization/policies) and opting out of authorization at a subset of public endpoints, see the [Razor Pages OIDC guidance](xref:security/authentication/configure-oidc-web-authentication#force-authorization).
433+
* The `Weather` component uses the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to prevent unauthorized access. For more information on requiring authorization across the app via an [authorization policy](xref:security/authorization/policies) and opting out of authorization at a subset of public endpoints, see the [Razor Pages OIDC guidance](xref:security/authentication/configure-oidc-web-authentication#force-authorization). For more information on how this app secures weather data, see [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering).
434434

435435
:::zone-end
436436

@@ -1022,3 +1022,4 @@ At this point, Razor components can adopt [role-based and policy-based authoriza
10221022
* [`AuthenticationStateProvider` service](xref:blazor/security/index#authenticationstateprovider-service)
10231023
* [Manage authentication state in Blazor Web Apps](xref:blazor/security/index#manage-authentication-state-in-blazor-web-apps)
10241024
* [Refresh token during http request in Blazor Interactive Server with OIDC (`dotnet/aspnetcore` #55213)](https://github.com/dotnet/aspnetcore/issues/55213)
1025+
* [Secure data in Blazor Web Apps with Interactive Auto rendering](xref:blazor/security/index#secure-data-in-blazor-web-apps-with-interactive-auto-rendering)

aspnetcore/blazor/security/index.md

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,183 @@ To handle authentication, use the built-in or custom <xref:Microsoft.AspNetCore.
583583

584584
For more information on client-side authentication, see <xref:blazor/security/webassembly/index>.
585585

586+
:::moniker range=">= aspnetcore-8.0"
587+
588+
## Secure data in Blazor Web Apps with Interactive Auto rendering
589+
590+
When a Blazor Web App adopts server-side rendering (SSR) and client-side rendering (CSR) for components or an entire app that specifies the [Interactive Auto render mode](xref:blazor/components/render-modes#automatic-auto-rendering), authorization to access components and data is applied in *two places*. The component restricts access to itself (and any data that it obtains) when rendered on the server by virtue of an authorization attribute in the component's definition file (`@attribute [Authorize]`). When the component is rendered on the client, access to data is restricted via the server web API endpoints that are called from the client. Care must be taken when securing data access in both locations to prevent improper data access.
591+
592+
Consider the following scenario where secure weather data is displayed by a component. The following example can be examined and demonstrated in a running sample app with either the `BlazorWebAppEntra` sample (.NET 9 or later) or the `BlazorWebAppOidc` sample (.NET 8 or later) in the [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps)).
593+
594+
The client project maintains a `WeatherForecast` class to hold weather data:
595+
596+
```csharp
597+
public sealed class WeatherForecast(DateOnly date, int temperatureC, string summary)
598+
{
599+
public DateOnly Date { get; set; } = date;
600+
public int TemperatureC { get; set; } = temperatureC;
601+
public string? Summary { get; set; } = summary;
602+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
603+
}
604+
```
605+
606+
The client project's `IWeatherForecaster` interface defines a `GetWeatherForecastAsync` method for obtaining weather data:
607+
608+
```csharp
609+
public interface IWeatherForecaster
610+
{
611+
Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync();
612+
}
613+
```
614+
615+
The client project's `ClientWeatherForecaster` service implements `IWeatherForecaster`. The `GetWeatherForecastAsync` method calls a web API in the server project at the `/weather-forecast` endpoint for weather data:
616+
617+
```csharp
618+
internal sealed class ClientWeatherForecaster(HttpClient httpClient)
619+
: IWeatherForecaster
620+
{
621+
public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync() =>
622+
await httpClient.GetFromJsonAsync<WeatherForecast[]>("/weather-forecast") ??
623+
throw new IOException("No weather forecast!");
624+
}
625+
```
626+
627+
The client project maintains a `Weather` component that:
628+
629+
* Enforces authorization with an [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute).
630+
* Uses the [Persistent Component State service](xref:blazor/components/prerender#persist-prerendered-state) (<xref:Microsoft.AspNetCore.Components.PersistentComponentState>) to persist weather forecast data when the component transitions from static to interactive SSR on the server.
631+
632+
```razor
633+
@page "/weather"
634+
@using Microsoft.AspNetCore.Authorization
635+
@using BlazorWebAppEntra.Client.Weather
636+
@attribute [Authorize]
637+
@implements IDisposable
638+
@inject PersistentComponentState ApplicationState
639+
@inject IWeatherForecaster WeatherForecaster
640+
641+
<PageTitle>Weather</PageTitle>
642+
643+
<h1>Weather</h1>
644+
645+
<p>This component demonstrates showing data.</p>
646+
647+
@if (forecasts == null)
648+
{
649+
<p><em>Loading...</em></p>
650+
}
651+
else
652+
{
653+
<table class="table">
654+
<thead>
655+
<tr>
656+
<th>Date</th>
657+
<th aria-label="Temperature in Celsius">Temp. (C)</th>
658+
<th aria-label="Temperature in Fahrenheit">Temp. (F)</th>
659+
<th>Summary</th>
660+
</tr>
661+
</thead>
662+
<tbody>
663+
@foreach (var forecast in forecasts)
664+
{
665+
<tr>
666+
<td>@forecast.Date.ToShortDateString()</td>
667+
<td>@forecast.TemperatureC</td>
668+
<td>@forecast.TemperatureF</td>
669+
<td>@forecast.Summary</td>
670+
</tr>
671+
}
672+
</tbody>
673+
</table>
674+
}
675+
676+
@code {
677+
private IEnumerable<WeatherForecast>? forecasts;
678+
private PersistingComponentStateSubscription persistingSubscription;
679+
680+
protected override async Task OnInitializedAsync()
681+
{
682+
persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData);
683+
684+
if (!ApplicationState.TryTakeFromJson<IEnumerable<WeatherForecast>>(
685+
nameof(forecasts), out var restoredData))
686+
{
687+
forecasts = await WeatherForecaster.GetWeatherForecastAsync();
688+
}
689+
else
690+
{
691+
forecasts = restoredData!;
692+
}
693+
}
694+
695+
private Task PersistData()
696+
{
697+
ApplicationState.PersistAsJson(nameof(forecasts), forecasts);
698+
699+
return Task.CompletedTask;
700+
}
701+
702+
void IDisposable.Dispose() => persistingSubscription.Dispose();
703+
}
704+
```
705+
706+
The server project implements `IWeatherForecaster` as `ServerWeatherForecaster`, which generates and returns mock weather data via its `GetWeatherForecastAsync` method:
707+
708+
```csharp
709+
public class ServerWeatherForecaster() : IWeatherForecaster
710+
{
711+
public readonly string[] summaries =
712+
[
713+
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot",
714+
"Sweltering", "Scorching"
715+
];
716+
717+
public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync()
718+
{
719+
// Simulate asynchronous loading to demonstrate streaming rendering
720+
await Task.Delay(500);
721+
722+
return Enumerable.Range(1, 5).Select(index =>
723+
new WeatherForecast
724+
(
725+
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
726+
Random.Shared.Next(-20, 55),
727+
summaries[Random.Shared.Next(summaries.Length)]
728+
))
729+
.ToArray();
730+
}
731+
}
732+
```
733+
734+
The server project maintains a secure web API endpoint for client weather data calls:
735+
736+
```csharp
737+
app.MapGet("/weather-forecast", (
738+
[FromServices] IWeatherForecaster WeatherForecaster) =>
739+
{
740+
return WeatherForecaster.GetWeatherForecastAsync();
741+
}).RequireAuthorization();
742+
```
743+
744+
Using the preceding approach, there are two systems in place to supply secure weather data to the user:
745+
746+
* When the `Weather` component is rendered *on the server*, the `ServerWeatherForecaster` service's `GetWeatherForecastAsync` method is used directly to obtain the weather data. The security of the data is enforced by the component's [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute). In summary, the security of the weather data is enforced by the component.
747+
* When the `Weather` component is rendered *on the client*, the `ClientWeatherForecaster` service is used to make a web API call to the secure `/weather-forecast` endpoint that applies the <xref:Microsoft.AspNetCore.Builder.AuthorizationEndpointConventionBuilderExtensions.RequireAuthorization%2A> extension method. If the user has the authority to access weather data, the endpoint uses the `ServerWeatherForecaster` service to call `GetWeatherForecastAsync`. The data is returned to the client. In summary, the security of the weather data is enforced by the server app's web API endpoint.
748+
749+
The preceding approach works well when the security requirements of the web API match the security requirements of the component. For example, the same authorization policy can be applied to both the web API endpoint and the component.
750+
751+
Complex scenarios require additional planning and implementation. For example, a server web API that has multiple callers with different access permissions either requires a more sophisticated authorization policy, one or more additional policies, or additional endpoints with different access requirements.
752+
753+
As you build security into apps that adopt Interactive Auto rendering, be mindful that the security implemented for the server's web API endpoints doesn't secure the server's service implementation that's used when a component is rendered on the server and accesses data through the service. Carefully weigh the difference between accessing data on the server during SSR versus accessing the data on a client web API request during CSR. Strategically apply security to avoid improper access to data.
754+
755+
Examples in the [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples/) ([how to download](xref:blazor/fundamentals/index#sample-apps)) that demonstrate the approach described in this section:
756+
757+
* `BlazorWebAppOidc`
758+
* `BlazorWebAppOidcBff`
759+
* `BlazorWebAppEntra`
760+
761+
:::moniker-end
762+
586763
## `AuthenticationStateProvider` service
587764

588765
:::moniker range=">= aspnetcore-8.0"

0 commit comments

Comments
 (0)