From d653a02bacc9c7a48b5606f9e4d1f8c35e56964a Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 16 May 2025 11:33:39 -0400 Subject: [PATCH 1/6] Add the latest recommended passing tokens approach --- .../blazor/security/additional-scenarios.md | 202 +++++++++++++++++- 1 file changed, 198 insertions(+), 4 deletions(-) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index ad42c86227b1..0c3f37a3e4ea 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -23,12 +23,206 @@ This article explains how to configure server-side Blazor for additional securit *This section applies to Blazor Web Apps. For Blazor Server, view the [7.0 version of this article section](xref:blazor/security/additional-scenarios?view=aspnetcore-7.0&preserve-view=true#pass-tokens-to-a-server-side-blazor-app).* -For more information, see the following issues: +If you merely want to use access tokens to make web API calls from a Blazor Web App with a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory), see the [Use a token handler for web API calls](#use-a-token-handler-for-web-api-calls) section, which explains how to use a implementation to attach a user's access token to outgoing requests. The following guidance in this section is for developers who need access tokens, refresh tokens, and other authentication properties throughout the app for general use. -* [Access `AuthenticationStateProvider` in outgoing request middleware (`dotnet/aspnetcore` #52379)](https://github.com/dotnet/aspnetcore/issues/52379): This is the current issue to address passing tokens in Blazor Web Apps with framework features, which will probably be addressed for .NET 11 (late 2026). -* [Problem providing Access Token to HttpClient in Interactive Server mode (`dotnet/aspnetcore` #52390)](https://github.com/dotnet/aspnetcore/issues/52390): This issue was closed as a duplicate of the preceding issue, but it contains helpful discussion and potential workaround strategies. +To save tokens and other authentication properties in Blazor Web Apps, we recommend putting them into user claims, which can be accessed from anywhere in the app, including on the client (in the `.Client` project) when [passing authentication state](xref:blazor/security/index#manage-authentication-state-in-blazor-web-apps) and setting to `true`. -For Blazor Server, view the [7.0 version of this article section](xref:blazor/security/additional-scenarios?view=aspnetcore-7.0&preserve-view=true#pass-tokens-to-a-server-side-blazor-app). +In the context of an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/security/blazor-web-app-oidc), the following example shows how to retain the access token of a user that just signed into the app. + +Where cookie authentication options (`CookieAuthenticationOptions`) are configured: + +```csharp +services.AddOptions(cookieScheme) + .Configure((cookieOptions, refresher) => +{ + cookieOptions.Events.OnValidatePrincipal = context => + refresher.ValidateOrRefreshCookieAsync(context, oidcScheme); + + cookieOptions.Events.OnSigningIn = (context) => + { + if (context.Principal?.Identity is not null && + context.Principal.Identity.IsAuthenticated) + { + var accessToken = context.Properties.GetTokenValue("access_token"); + var claimsIdentity = new ClaimsIdentity(context.Principal?.Identity, + [new Claim("AccessToken", accessToken ?? "No Access Token!")]); + context.Principal = new ClaimsPrincipal(claimsIdentity); + context.Properties.Items.Remove("access_token"); + } + + return Task.CompletedTask; + }; +}); +``` + +Where the principal is validated () to update user access tokens when they expire, the claim is also updated with the new access token by replacing the principal: + +```csharp +public async Task ValidateOrRefreshCookieAsync( + CookieValidatePrincipalContext validateContext, string oidcScheme) +{ + ... + + validationResult.Claims.Remove("AccessToken"); + validationResult.ClaimsIdentity.AddClaim( + new Claim("AccessToken", message.AccessToken)); + validateContext.ReplacePrincipal( + new ClaimsPrincipal(validationResult.ClaimsIdentity)); + + ... +} +``` + +App code and components, including components that render on the client, can use the claim to read tokens and authentication properties. In the following `ServerWeatherForecaster` service for obtaining weather data on the server, the `AccessToken` claim is used to make a secure call to a backend web API for weather data: + +```csharp +internal sealed class ServerWeatherForecaster(IHttpClientFactory clientFactory, + IHttpContextAccessor httpContextAccessor, IConfiguration config) + : IWeatherForecaster +{ + public async Task> GetWeatherForecastAsync() + { + var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast"); + var accessToken = httpContextAccessor.HttpContext?.User.Claims.First( + c => c.Type == "AccessToken").Value + ?? throw new Exception("No access token!"); + request.Headers.Authorization = + new AuthenticationHeaderValue("Bearer", accessToken); + var client = clientFactory.CreateClient(); + client.BaseAddress = new Uri(config["ExternalApiUri"] + ?? throw new Exception("No base address!")); + + var response = await client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadFromJsonAsync() ?? + throw new IOException("No weather forecast!"); + } +} +``` + +The following code demonstrates a similar approach in a component that calls a secure web API: + +:::moniker-end + +:::moniker range=">= aspnetcore-10.0" + +```razor +@page "/..." +@using Microsoft.AspNetCore.Authorization +@attribute [Authorize] + +... + +@code { + [CascadingParameter] + private Task? authenticationState { get; set; } + + [SupplyParameterFromPersistentComponentState] + public string AccessToken { get; set; } = "Not set!"; + + protected override async Task OnInitializedAsync() + { + if (authenticationState is not null) + { + var authState = await authenticationState; + var user = authState?.User; + + if (user is not null) + { + AccessToken ??= user.Claims.FirstOrDefault( + c => c.Type == "AccessToken")?.Value ?? "Not found!"; + + request.Headers.Authorization = + new AuthenticationHeaderValue("Bearer", AccessToken); + var client = clientFactory.CreateClient(); + client.BaseAddress = new Uri(...); + + var response = await client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadFromJsonAsync<...>() ?? + throw new IOException("No data!"); + } + } + } +} +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0 < aspnetcore-10.0" + +```razor +@page "/..." +@using Microsoft.AspNetCore.Authorization +@attribute [Authorize] +@implements IDisposable +@inject PersistentComponentState ApplicationState + +... + +@code { + private PersistingComponentStateSubscription persistingSubscription; + private string accessToken = "Not set!"; + + [CascadingParameter] + private Task? authenticationState { get; set; } + + protected override async Task OnInitializedAsync() + { + if (authenticationState is not null) + { + var authState = await authenticationState; + var user = authState?.User; + + if (user is not null) + { + if (!ApplicationState.TryTakeFromJson(nameof(accessToken), + out var restoredAccessToken)) + { + accessToken = user.Claims.FirstOrDefault( + c => c.Type == "AccessToken")?.Value ?? "Not found!"; + + request.Headers.Authorization = + new AuthenticationHeaderValue("Bearer", accessToken); + var client = clientFactory.CreateClient(); + client.BaseAddress = new Uri(...); + + var response = await client.SendAsync(request); + + response.EnsureSuccessStatusCode(); + + return await response.Content.ReadFromJsonAsync<...>() ?? + throw new IOException("No data!"); + } + else + { + accessToken = restoredAccessToken!; + } + } + } + + // Call at the end to avoid a potential race condition at app shutdown + persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData); + } + + private Task PersistData() + { + ApplicationState.PersistAsJson(nameof(accessToken), accessToken); + + return Task.CompletedTask; + } + + void IDisposable.Dispose() => persistingSubscription.Dispose(); +} +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" ## Reading tokens from `HttpContext` From 514c1b37f0c729a5dab7d5ad836e759588802956 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 10:37:24 -0400 Subject: [PATCH 2/6] Updates --- .../blazor/security/additional-scenarios.md | 193 ++++++++++-------- 1 file changed, 106 insertions(+), 87 deletions(-) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index 0c3f37a3e4ea..5bd755a42940 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -27,9 +27,18 @@ If you merely want to use access tokens to make web API calls from a Blazor Web To save tokens and other authentication properties in Blazor Web Apps, we recommend putting them into user claims, which can be accessed from anywhere in the app, including on the client (in the `.Client` project) when [passing authentication state](xref:blazor/security/index#manage-authentication-state-in-blazor-web-apps) and setting to `true`. -In the context of an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/security/blazor-web-app-oidc), the following example shows how to retain the access token of a user that just signed into the app. +The following scenarios are covered: -Where cookie authentication options (`CookieAuthenticationOptions`) are configured: +* [Passing tokens in apps that use an OIDC identity provider](#passing-tokens-in-apps-that-use-an-oidc-identity-provider) +* [Passing tokens in apps that use Microsoft Identity Web packages/API for Entra ID](#passing-tokens-in-apps-that-use-microsoft-identity-web-packagesapi-for-entra-id) + +### Passing tokens in apps that use an OIDC identity provider + +In the context of an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/security/blazor-web-app-oidc), the following example shows how to retain the access token of a user that just signed into the app in a claim and refresh the claim when a new access token is issued by the identity provider. + +The following example can be implemented in the sample app that accompanies for a local demonstration of the approach. + +Where cookie authentication options () are configured, create an "`AccessToken`" claim when the user is signing in (): ```csharp services.AddOptions(cookieScheme) @@ -55,7 +64,7 @@ services.AddOptions(cookieScheme) }); ``` -Where the principal is validated () to update user access tokens when they expire, the claim is also updated with the new access token by replacing the principal: +Where the principal is validated () to update the user's access token when it's renewed, update the `AccessToken` claim: ```csharp public async Task ValidateOrRefreshCookieAsync( @@ -73,6 +82,9 @@ public async Task ValidateOrRefreshCookieAsync( } ``` +> [!NOTE] +> Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . + App code and components, including components that render on the client, can use the claim to read tokens and authentication properties. In the following `ServerWeatherForecaster` service for obtaining weather data on the server, the `AccessToken` claim is used to make a secure call to a backend web API for weather data: ```csharp @@ -102,128 +114,135 @@ internal sealed class ServerWeatherForecaster(IHttpClientFactory clientFactory, } ``` -The following code demonstrates a similar approach in a component that calls a secure web API: - -:::moniker-end - -:::moniker range=">= aspnetcore-10.0" - -```razor -@page "/..." -@using Microsoft.AspNetCore.Authorization -@attribute [Authorize] +See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code. -... +### Passing tokens in apps that use Microsoft Identity Web packages/API for Entra ID -@code { - [CascadingParameter] - private Task? authenticationState { get; set; } +In a Blazor Web App with [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), use the event to store tokens and other authentication data in the user's claims principal. The following example shows how to retain and update the access token of a user. - [SupplyParameterFromPersistentComponentState] - public string AccessToken { get; set; } = "Not set!"; +The following example can be implemented in the sample app that accompanies for a local demonstration of the approach. - protected override async Task OnInitializedAsync() +```csharp +builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) + .AddMicrosoftIdentityWebApp(msIdentityOptions => { - if (authenticationState is not null) + ... + + msIdentityOptions.Events.OnTokenResponseReceived = context => { - var authState = await authenticationState; - var user = authState?.User; + var claim = context.Principal?.Claims.FirstOrDefault(c => c.Type == "AccessToken"); - if (user is not null) + if (context.Principal?.Identity is not null && claim is not null) { - AccessToken ??= user.Claims.FirstOrDefault( - c => c.Type == "AccessToken")?.Value ?? "Not found!"; + ((ClaimsIdentity)context.Principal.Identity).RemoveClaim(claim); + } - request.Headers.Authorization = - new AuthenticationHeaderValue("Bearer", AccessToken); - var client = clientFactory.CreateClient(); - client.BaseAddress = new Uri(...); + var tokens = context.TokenEndpointResponse; + var claimsIdentity = new ClaimsIdentity(); + claimsIdentity.AddClaim(new Claim("AccessToken", tokens.AccessToken)); + context.Principal?.AddIdentity(claimsIdentity); - var response = await client.SendAsync(request); + return Task.CompletedTask; + }; + }) + .AddInMemoryTokenCaches(); +``` - response.EnsureSuccessStatusCode(); +> [!NOTE] +> Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . - return await response.Content.ReadFromJsonAsync<...>() ?? - throw new IOException("No data!"); - } - } - } -} -``` +See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code. Note that we recommend using Microsoft Identity Web packages/API for Entra in most cases for the best developer experience, which is demonstrated by the sample app for . The `Weather` component demonstration is for demonstration purposes. -:::moniker-end +### Demonstration `Weather` component + +The following component is presented to demonstrate using an access token from the user's claims. -:::moniker range=">= aspnetcore-8.0 < aspnetcore-10.0" +[CORS](xref:security/cors) configuration is required when the web API is hosted at a different origin than the calling app. + +With additional code, the weather forecasts can be persisted when the component is prerendered. For more information, see . ```razor -@page "/..." +@page "/weather" +@using System.Net.Http.Headers @using Microsoft.AspNetCore.Authorization +@using BlazorWebAppEntra.Client.Weather @attribute [Authorize] -@implements IDisposable -@inject PersistentComponentState ApplicationState +@inject IHttpClientFactory ClientFactory -... +Weather + +

Weather

+ +

This component demonstrates showing data.

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
+} @code { - private PersistingComponentStateSubscription persistingSubscription; - private string accessToken = "Not set!"; + private IEnumerable? forecasts; [CascadingParameter] - private Task? authenticationState { get; set; } + private Task? AuthState { get; set; } protected override async Task OnInitializedAsync() { - if (authenticationState is not null) + if (AuthState is not null) { - var authState = await authenticationState; + var authState = await AuthState; var user = authState?.User; if (user is not null) { - if (!ApplicationState.TryTakeFromJson(nameof(accessToken), - out var restoredAccessToken)) - { - accessToken = user.Claims.FirstOrDefault( - c => c.Type == "AccessToken")?.Value ?? "Not found!"; - - request.Headers.Authorization = - new AuthenticationHeaderValue("Bearer", accessToken); - var client = clientFactory.CreateClient(); - client.BaseAddress = new Uri(...); - - var response = await client.SendAsync(request); - - response.EnsureSuccessStatusCode(); - - return await response.Content.ReadFromJsonAsync<...>() ?? - throw new IOException("No data!"); - } - else - { - accessToken = restoredAccessToken!; - } - } - } + var accessToken = + user.Claims.First(c => c.Type == "AccessToken")?.Value; - // Call at the end to avoid a potential race condition at app shutdown - persistingSubscription = ApplicationState.RegisterOnPersisting(PersistData); - } + var request = + new HttpRequestMessage(HttpMethod.Get, "/weather-forecast"); - private Task PersistData() - { - ApplicationState.PersistAsJson(nameof(accessToken), accessToken); + request.Headers.Authorization = + new AuthenticationHeaderValue("Bearer", accessToken); + var client = ClientFactory.CreateClient(); + client.BaseAddress = new Uri("https://localhost:7277"); - return Task.CompletedTask; - } + var response = await client.SendAsync(request); + + response.EnsureSuccessStatusCode(); - void IDisposable.Dispose() => persistingSubscription.Dispose(); + forecasts = await response.Content + .ReadFromJsonAsync>() ?? + throw new IOException("No data!"); + } + } + } } ``` -:::moniker-end - -:::moniker range=">= aspnetcore-8.0" - ## Reading tokens from `HttpContext` Reading tokens from , including as a [cascading parameter](xref:Microsoft.AspNetCore.Components.CascadingParameterAttribute), using is supported for obtaining tokens for use during interactive server rendering if the tokens are obtained during static server-side rendering (static SSR) or prerendering. However, tokens aren't updated if the user authenticates after the circuit is established, since the is captured at the start of the SignalR connection. Also, the use of by means that you must be careful not to lose the execution context before reading the . From 22547cbfb1bcadb0984e7c48c9e7c496e993b0c3 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 11:05:16 -0400 Subject: [PATCH 3/6] Updates --- .../blazor/security/additional-scenarios.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index 5bd755a42940..dda0b4b3b2a4 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -23,7 +23,7 @@ This article explains how to configure server-side Blazor for additional securit *This section applies to Blazor Web Apps. For Blazor Server, view the [7.0 version of this article section](xref:blazor/security/additional-scenarios?view=aspnetcore-7.0&preserve-view=true#pass-tokens-to-a-server-side-blazor-app).* -If you merely want to use access tokens to make web API calls from a Blazor Web App with a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory), see the [Use a token handler for web API calls](#use-a-token-handler-for-web-api-calls) section, which explains how to use a implementation to attach a user's access token to outgoing requests. The following guidance in this section is for developers who need access tokens, refresh tokens, and other authentication properties throughout the app for general use. +If you merely want to use access tokens to make web API calls from a Blazor Web App with a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory), see the [Use a token handler for web API calls](#use-a-token-handler-for-web-api-calls) section, which explains how to use a implementation to attach a user's access token to outgoing requests. The guidance in this section is for developers who need access tokens, refresh tokens, and other authentication properties throughout the app for other use cases. To save tokens and other authentication properties in Blazor Web Apps, we recommend putting them into user claims, which can be accessed from anywhere in the app, including on the client (in the `.Client` project) when [passing authentication state](xref:blazor/security/index#manage-authentication-state-in-blazor-web-apps) and setting to `true`. @@ -34,7 +34,7 @@ The following scenarios are covered: ### Passing tokens in apps that use an OIDC identity provider -In the context of an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/security/blazor-web-app-oidc), the following example shows how to retain the access token of a user that just signed into the app in a claim and refresh the claim when a new access token is issued by the identity provider. +In the context of an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/security/blazor-web-app-oidc), the following example shows how to retain the access token of a user in a claim and refresh the claim when a new access token is issued by the identity provider. The following example can be implemented in the sample app that accompanies for a local demonstration of the approach. @@ -64,7 +64,7 @@ services.AddOptions(cookieScheme) }); ``` -Where the principal is validated () to update the user's access token when it's renewed, update the `AccessToken` claim: +In the event handler assigned to where the user's access token renewed on expiration, update the `AccessToken` claim: ```csharp public async Task ValidateOrRefreshCookieAsync( @@ -82,6 +82,8 @@ public async Task ValidateOrRefreshCookieAsync( } ``` +For more information on the `ValidateOrRefreshCookieAsync` method that we recommend for refreshing cookies, see , which includes a full implementation of the preceding method in its sample app. + > [!NOTE] > Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . @@ -114,13 +116,13 @@ internal sealed class ServerWeatherForecaster(IHttpClientFactory clientFactory, } ``` -See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code. +See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code using an . ### Passing tokens in apps that use Microsoft Identity Web packages/API for Entra ID In a Blazor Web App with [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), use the event to store tokens and other authentication data in the user's claims principal. The following example shows how to retain and update the access token of a user. -The following example can be implemented in the sample app that accompanies for a local demonstration of the approach. +The following example can be implemented in the sample app that accompanies for a local demonstration of the approach. See the sample app that accompanies the article for a full implementation of the approach. ```csharp builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) @@ -151,11 +153,11 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) > [!NOTE] > Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . -See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code. Note that we recommend using Microsoft Identity Web packages/API for Entra in most cases for the best developer experience, which is demonstrated by the sample app for . The `Weather` component demonstration is for demonstration purposes. +See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code. Note that we recommend using Microsoft Identity Web packages/API for Entra in most cases for the best developer experience, which is demonstrated by the sample app for . The `Weather` component is for demonstration purposes. ### Demonstration `Weather` component -The following component is presented to demonstrate using an access token from the user's claims. +The following component is presented to demonstrate using an access token from the user's claims in C# code using an . [CORS](xref:security/cors) configuration is required when the web API is hosted at a different origin than the calling app. From 40e531a1db6b3d1b149e9ca925e4b7072efea09a Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 12:46:16 -0400 Subject: [PATCH 4/6] Updates --- aspnetcore/blazor/call-web-api.md | 6 ++-- .../blazor/security/additional-scenarios.md | 36 ++++++++++--------- .../security/blazor-web-app-with-entra.md | 2 +- aspnetcore/blazor/security/index.md | 2 +- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 6afd6bc62f18..d83946f8717b 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -33,7 +33,7 @@ For more information, see the following resources: ## Microsoft identity platform for web API calls -Blazor Web Apps that use use [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) can make streamlined web API calls with API provided by the [`Microsoft.Identity.Web.DownstreamApi` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web.DownstreamApi). +Blazor Web Apps that use use [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) can make streamlined web API calls with API provided by the [`Microsoft.Identity.Web.DownstreamApi` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web.DownstreamApi). [!INCLUDE[](~/includes/package-reference.md)] @@ -210,13 +210,13 @@ The solution includes a demonstration of obtaining weather data securely via an ### `BlazorWebAppEntra` -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 an external web API when a component that adopts Interactive Auto rendering is rendered on the client. +A Blazor Web App with global Auto interactivity that uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/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 an external web API when a component that adopts Interactive Auto rendering is rendered on the client. ### `BlazorWebAppEntraBff` 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). +* [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra). * 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. The solution includes a demonstration of obtaining weather data securely via an external web API when a component that adopts Interactive Auto rendering is rendered on the client. diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index dda0b4b3b2a4..b9ef84b18507 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -23,7 +23,7 @@ This article explains how to configure server-side Blazor for additional securit *This section applies to Blazor Web Apps. For Blazor Server, view the [7.0 version of this article section](xref:blazor/security/additional-scenarios?view=aspnetcore-7.0&preserve-view=true#pass-tokens-to-a-server-side-blazor-app).* -If you merely want to use access tokens to make web API calls from a Blazor Web App with a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory), see the [Use a token handler for web API calls](#use-a-token-handler-for-web-api-calls) section, which explains how to use a implementation to attach a user's access token to outgoing requests. The guidance in this section is for developers who need access tokens, refresh tokens, and other authentication properties throughout the app for other use cases. +If you merely want to use access tokens to make web API calls from a Blazor Web App with a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory), see the [Use a token handler for web API calls](#use-a-token-handler-for-web-api-calls) section, which explains how to use a implementation to attach a user's access token to outgoing requests. The guidance in this section is for developers who need access tokens, refresh tokens, and other authentication properties throughout the app for other purposes. To save tokens and other authentication properties in Blazor Web Apps, we recommend putting them into user claims, which can be accessed from anywhere in the app, including on the client (in the `.Client` project) when [passing authentication state](xref:blazor/security/index#manage-authentication-state-in-blazor-web-apps) and setting to `true`. @@ -34,11 +34,14 @@ The following scenarios are covered: ### Passing tokens in apps that use an OIDC identity provider -In the context of an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/security/blazor-web-app-oidc), the following example shows how to retain the access token of a user in a claim and refresh the claim when a new access token is issued by the identity provider. +For an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/security/blazor-web-app-oidc), the following example shows how to: -The following example can be implemented in the sample app that accompanies for a local demonstration of the approach. +* Retain the access token of a user in a claim when the user signs into the app. +* Update the claim when the authentication cookie is updated with a new access token on principal validation. -Where cookie authentication options () are configured, create an "`AccessToken`" claim when the user is signing in (): +For a local demonstration of the approach, you can implement the following example in the sample app that accompanies . + +Where cookie authentication options () are configured, create an access token claim, named "`AccessToken`," when the user signs into the app (): ```csharp services.AddOptions(cookieScheme) @@ -64,7 +67,7 @@ services.AddOptions(cookieScheme) }); ``` -In the event handler assigned to where the user's access token renewed on expiration, update the `AccessToken` claim: +In the event handler assigned to , where the user's access token renewed on expiration, update the `AccessToken` claim: ```csharp public async Task ValidateOrRefreshCookieAsync( @@ -82,12 +85,11 @@ public async Task ValidateOrRefreshCookieAsync( } ``` -For more information on the `ValidateOrRefreshCookieAsync` method that we recommend for refreshing cookies, see , which includes a full implementation of the preceding method in its sample app. +For sample code that fully implements the preceding `ValidateOrRefreshCookieAsync` method, see and the sample app that accompanies the article. -> [!NOTE] -> Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . +Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . -App code and components, including components that render on the client, can use the claim to read tokens and authentication properties. In the following `ServerWeatherForecaster` service for obtaining weather data on the server, the `AccessToken` claim is used to make a secure call to a backend web API for weather data: +App code and components, including components that render on the client, can use claims to read tokens and authentication properties. In the following `ServerWeatherForecaster` service for obtaining weather data on the server, the `AccessToken` claim is used to make a secure call to an external backend web API for weather data: ```csharp internal sealed class ServerWeatherForecaster(IHttpClientFactory clientFactory, @@ -116,13 +118,13 @@ internal sealed class ServerWeatherForecaster(IHttpClientFactory clientFactory, } ``` -See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code using an . +See the [Demonstration `Weather` component](#demonstration-weather-component) section for an example component that obtains weather data from a web API in developer code using an . ### Passing tokens in apps that use Microsoft Identity Web packages/API for Entra ID -In a Blazor Web App with [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), use the event to store tokens and other authentication data in the user's claims principal. The following example shows how to retain and update the access token of a user. +In a Blazor Web App with [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra), use the event to store tokens and other authentication data in the user's claims principal. The following example shows how to retain and update the access token of a user. -The following example can be implemented in the sample app that accompanies for a local demonstration of the approach. See the sample app that accompanies the article for a full implementation of the approach. +For a local demonstration of the approach, you can implement the following example in the sample app that accompanies . ```csharp builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) @@ -150,15 +152,17 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddInMemoryTokenCaches(); ``` -> [!NOTE] -> Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . +Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . -See the [Demonstration `Weather` component](#demonstration-weather-component) section for a demonstration `Weather` component that obtains weather data from a web API in developer code. Note that we recommend using Microsoft Identity Web packages/API for Entra in most cases for the best developer experience, which is demonstrated by the sample app for . The `Weather` component is for demonstration purposes. +See the [Demonstration `Weather` component](#demonstration-weather-component) section for an example component that obtains weather data from a web API in developer code using an . However, we recommend using Microsoft Identity Web packages/API for Entra in most cases for the best developer experience, which is demonstrated by the sample app described by . The `Weather` component in the *Demonstration `Weather` component* section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. ### Demonstration `Weather` component The following component is presented to demonstrate using an access token from the user's claims in C# code using an . +> [!NOTE] +> If the app uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra), we recommend using the API provided by Microsoft to call web APIs, which provides a better developer experience. The example in this section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. + [CORS](xref:security/cors) configuration is required when the web API is hosted at a different origin than the calling app. With additional code, the weather forecasts can be persisted when the component is prerendered. For more information, see . @@ -257,7 +261,7 @@ The following approach is aimed at attaching a user's access token to outgoing r For a demonstration of the guidance in this section, see the `BlazorWebAppOidc` and `BlazorWebAppOidcServer` sample apps (.NET 8 or later) in the [Blazor samples GitHub repository](https://github.com/dotnet/blazor-samples). The samples adopt a global interactive render mode and OIDC authentication with Microsoft Entra without using Entra-specific packages. The samples demonstrate how to pass a JWT access token to call a secure web API. -[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) provides a API to call web APIs from Blazor Web Apps with automatic token management and renewal. For more information, see and the `BlazorWebAppEntra` and `BlazorWebAppEntraBff` sample apps (.NET 9 or later) in the [Blazor samples GitHub repository](https://github.com/dotnet/blazor-samples). +[Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) provides a API to call web APIs from Blazor Web Apps with automatic token management and renewal. For more information, see and the `BlazorWebAppEntra` and `BlazorWebAppEntraBff` sample apps (.NET 9 or later) in the [Blazor samples GitHub repository](https://github.com/dotnet/blazor-samples). Subclass to attach a user's access token to outgoing requests. The token handler only executes on the server, so using is safe. diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index 7a20c11d7219..8027dd886ca4 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -17,7 +17,7 @@ zone_pivot_groups: blazor-web-app-entra-specification --> -This article describes how to secure a Blazor Web App with [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) using a sample app. +This article describes how to secure a Blazor Web App with [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) using a sample app. :::zone pivot="non-bff-pattern" diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index be9946be18f5..313c8dc4b95c 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -817,7 +817,7 @@ internal sealed class ServerWeatherForecaster(IHttpClientFactory clientFactory) } ``` -If the app 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) (see ), the `ServerWeatherForecaster` might appear like the following class to make external web API calls: +If the app uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) (see ), the `ServerWeatherForecaster` might appear like the following class to make external web API calls: ```csharp internal sealed class ServerWeatherForecaster(IDownstreamApi downstreamApi) : IWeatherForecaster From b8eb5cd135f0c4cf27e9844ef2204101db291aa7 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 13:39:24 -0400 Subject: [PATCH 5/6] Updates --- aspnetcore/blazor/security/additional-scenarios.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index b9ef84b18507..a51fc7696f73 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -41,7 +41,7 @@ For an app that adopts [OpenId Connect (OIDC) authentication](xref:blazor/securi For a local demonstration of the approach, you can implement the following example in the sample app that accompanies . -Where cookie authentication options () are configured, create an access token claim, named "`AccessToken`," when the user signs into the app (): +Where cookie authentication options () are configured, create an access token claim, which is named "`AccessToken`" in the following example, when the user signs into the app (): ```csharp services.AddOptions(cookieScheme) @@ -89,7 +89,7 @@ For sample code that fully implements the preceding `ValidateOrRefreshCookieAsyn Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . -App code and components, including components that render on the client, can use claims to read tokens and authentication properties. In the following `ServerWeatherForecaster` service for obtaining weather data on the server, the `AccessToken` claim is used to make a secure call to an external backend web API for weather data: +Classes and components, including components that render on the client, can read tokens and other authentication property data from claims. In the following `ServerWeatherForecaster` service for obtaining weather data on the server, the `AccessToken` claim is used to make a secure call to an external web API for weather data: ```csharp internal sealed class ServerWeatherForecaster(IHttpClientFactory clientFactory, @@ -154,14 +154,14 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) Make sure that is set to `true` if you need the authentication data client-side (in the `.Client` project of the Blazor Web App). For more information, see . -See the [Demonstration `Weather` component](#demonstration-weather-component) section for an example component that obtains weather data from a web API in developer code using an . However, we recommend using Microsoft Identity Web packages/API for Entra in most cases for the best developer experience, which is demonstrated by the sample app described by . The `Weather` component in the *Demonstration `Weather` component* section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. +See the [Demonstration `Weather` component](#demonstration-weather-component) section for an example component that obtains weather data from a web API in developer code using an . However, we recommend using Microsoft Identity Web packages/API for Entra in most cases for the best developer experience, which is demonstrated by the sample app for . The `Weather` component in the *Demonstration `Weather` component* section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. ### Demonstration `Weather` component The following component is presented to demonstrate using an access token from the user's claims in C# code using an . > [!NOTE] -> If the app uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra), we recommend using the API provided by Microsoft to call web APIs, which provides a better developer experience. The example in this section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. +> If the app uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra), we recommend using the API provided by Microsoft to call web APIs, which usually provide a better developer experience. The example in this section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. [CORS](xref:security/cors) configuration is required when the web API is hosted at a different origin than the calling app. From c97015fb72f489d3ea405435771ccf1d7ef72cf9 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 19 May 2025 13:57:55 -0400 Subject: [PATCH 6/6] Updates --- aspnetcore/blazor/security/additional-scenarios.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/aspnetcore/blazor/security/additional-scenarios.md b/aspnetcore/blazor/security/additional-scenarios.md index a51fc7696f73..133f395ed932 100644 --- a/aspnetcore/blazor/security/additional-scenarios.md +++ b/aspnetcore/blazor/security/additional-scenarios.md @@ -67,7 +67,7 @@ services.AddOptions(cookieScheme) }); ``` -In the event handler assigned to , where the user's access token renewed on expiration, update the `AccessToken` claim: +In the event handler assigned to , where the user's access token is renewed on expiration, update the `AccessToken` claim: ```csharp public async Task ValidateOrRefreshCookieAsync( @@ -161,11 +161,11 @@ See the [Demonstration `Weather` component](#demonstration-weather-component) se The following component is presented to demonstrate using an access token from the user's claims in C# code using an . > [!NOTE] -> If the app uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra), we recommend using the API provided by Microsoft to call web APIs, which usually provide a better developer experience. The example in this section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. +> If the app uses [Microsoft identity platform](/entra/identity-platform/) with [Microsoft Identity Web packages/API](/entra/msal/dotnet/microsoft-identity-web/) for [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra), we recommend using the API provided by Microsoft to call web APIs, which usually provides a better developer experience. The example in this section is only for demonstration purposes in an app that adopts Microsoft Identity Web for Entra. [CORS](xref:security/cors) configuration is required when the web API is hosted at a different origin than the calling app. -With additional code, the weather forecasts can be persisted when the component is prerendered. For more information, see . +With additional code, the weather forecast can be persisted when the component is prerendered. For more information, see . ```razor @page "/weather" @@ -181,7 +181,7 @@ With additional code, the weather forecasts can be persisted when the component

This component demonstrates showing data.

-@if (forecasts == null) +@if (forecast == null) {

Loading...

} @@ -197,7 +197,7 @@ else - @foreach (var forecast in forecasts) + @foreach (var forecast in forecast) { @forecast.Date.ToShortDateString() @@ -211,7 +211,7 @@ else } @code { - private IEnumerable? forecasts; + private IEnumerable? forecast; [CascadingParameter] private Task? AuthState { get; set; } @@ -240,7 +240,7 @@ else response.EnsureSuccessStatusCode(); - forecasts = await response.Content + forecast = await response.Content .ReadFromJsonAsync>() ?? throw new IOException("No data!"); }