From 9c0bbb8234178235b2d48748acb6aa6090378cc8 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 18 Dec 2025 08:46:09 -0500 Subject: [PATCH 1/3] Rename/reorder BWA+OIDC pivots and samples --- aspnetcore/blazor/call-web-api.md | 4 +- .../security/blazor-web-app-with-entra.md | 4 + .../security/blazor-web-app-with-oidc.md | 456 +++++++++--------- .../configure-jwt-bearer-authentication.md | 6 +- aspnetcore/zone-pivot-groups.yml | 12 +- 5 files changed, 244 insertions(+), 238 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index f3b68531f510..58e793f25d78 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -28,8 +28,8 @@ For more information, see the following resources: * * *Secure an ASP.NET Core Blazor Web App with OpenID Connect (OIDC)* - * [Non-BFF pattern (Interactive Auto)](xref:blazor/security/blazor-web-app-oidc?pivots=non-bff-pattern) - * [Non-BFF pattern (Interactive Server)](xref:blazor/security/blazor-web-app-oidc?pivots=non-bff-pattern-server) + * [Without YARP and Aspire (Interactive Auto)](xref:blazor/security/blazor-web-app-oidc?pivots=without-yarp-and-aspire) + * [Without YARP and Aspire (Interactive Server)](xref:blazor/security/blazor-web-app-oidc?pivots=without-yarp-and-aspire-server) ## Microsoft identity platform for web API calls diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index 37070fe6bab7..e4512509ad2f 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -64,6 +64,8 @@ Start the solution from the ***`Aspire/Aspire.AppHost` project***. We recommend using separate registrations for apps and web APIs, even when the apps and web APIs are in the same solution. The following guidance is for the `BlazorWebAppEntra` app and `MinimalApiJwt` web API of the sample solution, but the same guidance applies generally to any Entra-based registrations for apps and web APIs. +For app and web API registration guidance, see [Register an application in Microsoft Entra ID](/entra/identity-platform/quickstart-register-app). + Register the web API (`MinimalApiJwt`) first so that you can then grant access to the web API when registering the app. The web API's tenant ID and client ID are used to configure the web API in its `Program` file. After registering the web API, expose the web API in **App registrations** > **Expose an API** with a scope name of `Weather.Get`. Record the App ID URI for use in the app's configuration. Next, register the app (`BlazorWebAppEntra`) with a **Web** platform configuration with two entries under **Redirect URI**: `https://localhost/signin-oidc` and `https://localhost/signout-callback-oidc` (ports aren't required on these URIs). Set the **Front-channel logout URL** to `https://localhost/signout-callback-oidc` (a port isn't required). The app's tenant ID, tenant domain, and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `appsettings.json` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. @@ -366,6 +368,8 @@ Access the sample through the latest version folder in the Blazor samples reposi We recommend using separate registrations for apps and web APIs, even when the apps and web APIs are in the same solution. The following guidance is for the `BlazorWebAppEntra` app and `MinimalApiJwt` web API of the sample solution, but the same guidance applies generally to any Entra-based registrations for apps and web APIs. +For app and web API registration guidance, see [Register an application in Microsoft Entra ID](/entra/identity-platform/quickstart-register-app). + Register the web API (`MinimalApiJwt`) first so that you can then grant access to the web API when registering the app. The web API's tenant ID and client ID are used to configure the web API in its `Program` file. After registering the web API, expose the web API in **App registrations** > **Expose an API** with a scope name of `Weather.Get`. Record the App ID URI for use in the app's configuration. Next, register the app (`BlazorWebAppEntra`) with a **Web** platform configuration with two entries under **Redirect URI**: `https://localhost/signin-oidc` and `https://localhost/signout-callback-oidc` (ports aren't required on these URIs). Set the **Front-channel logout URL** to `https://localhost/signout-callback-oidc` (a port isn't required). The app's tenant ID, tenant domain, and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `appsettings.json` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. diff --git a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md index d2747fa5db0d..a57009104df3 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md @@ -5,7 +5,7 @@ description: Learn how to secure a Blazor Web App with OpenID Connect (OIDC). monikerRange: '>= aspnetcore-8.0' ms.author: wpickett ms.custom: mvc -ms.date: 11/11/2025 +ms.date: 12/18/2025 uid: blazor/security/blazor-web-app-oidc zone_pivot_groups: blazor-web-app-oidc-specification --- @@ -15,7 +15,7 @@ zone_pivot_groups: blazor-web-app-oidc-specification This article describes how to secure a Blazor Web App with [OpenID Connect (OIDC)](https://openid.net/developers/how-connect-works/) using a sample app in the [`dotnet/blazor-samples` GitHub repository (.NET 8 or later)](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps)). -:::zone pivot="non-bff-pattern" +:::zone pivot="with-yarp-and-aspire" :::moniker range=">= aspnetcore-9.0" @@ -23,60 +23,61 @@ For Microsoft Entra ID or Azure AD B2C, you can use . +Also, see the *Prerequisites* section of [Quickstart: Build your first Aspire solution](/dotnet/aspire/get-started/build-your-first-aspire-app?tabs=visual-studio#prerequisites). ## Sample solution The sample app consists of the following projects: -* `BlazorWebAppOidc`: Server-side project of the Blazor Web App, containing an example [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. +* Aspire: + * `Aspire.AppHost`: Used to manage the high-level orchestration concerns of the app. + * `Aspire.ServiceDefaults`: Contains default Aspire app configurations that can be extended and customized as needed. +* `MinimalApiJwt`: Backend web API, containing an example [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. +* `BlazorWebAppOidc`: Server-side project of the Blazor Web App. The project uses [YARP](https://dotnet.github.io/yarp/) to proxy requests to a weather forecast endpoint in the backend web API project (`MinimalApiJwt`) with the `access_token` stored in the authentication cookie. * `BlazorWebAppOidc.Client`: Client-side project of the Blazor Web App. -* `MinimalApiJwt`: Backend web API with a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. -Access the sample through the latest version folder in the Blazor samples repository with the following link. The sample is in the `BlazorWebAppOidc` folder for .NET 8 or later. +Access the sample through the latest version folder in the Blazor samples repository with the following link. The sample is in the `BlazorWebAppOidcBffAutoYarpAspire` folder for .NET 8 or later. Start the solution from the ***`Aspire/Aspire.AppHost` project***. [View or download sample code](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps)) -Sample solution features: - -* Automatic non-interactive token refresh with the help of a custom cookie refresher (`CookieOidcRefresher.cs`). - -* Weather data is handled by a Minimal API endpoint (`/weather-forecast`) in the `Program` file (`Program.cs`) of the `MinimalApiJwt` project. The endpoint requires authorization by calling . For any controllers that you add to the project, add the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the controller or action. 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). - -* The app securely calls a web API for weather data: - - * When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` on the server to obtain weather data from the web API in the `MinimalApiJwt` project using a (`TokenHandler`) that attaches the access token from the to the request. - * When the component is rendered on the client, the component uses the `ClientWeatherForecaster` service implementation, which uses a preconfigured (in the client project's `Program` file) to make the web API call from the server project's `ServerWeatherForecaster`. +The Blazor Web App uses [the Auto render mode with global interactivity](xref:blazor/components/render-modes). :::moniker range=">= aspnetcore-9.0" -* The server project calls to add a server-side authentication state provider that uses to flow the authentication state to the client. The client calls to deserialize and use the authentication state passed by the server. The authentication state is fixed for the lifetime of the WebAssembly application. +The server project calls to add a server-side authentication state provider that uses to flow the authentication state to the client. The client calls to deserialize and use the authentication state passed by the server. The authentication state is fixed for the lifetime of the WebAssembly application. :::moniker-end :::moniker range="< aspnetcore-9.0" -* The `PersistingAuthenticationStateProvider` class (`PersistingAuthenticationStateProvider.cs`) is a server-side that uses to flow the authentication state to the client, which is then fixed for the lifetime of the WebAssembly application. +The `PersistingAuthenticationStateProvider` class (`PersistingAuthenticationStateProvider.cs`) is a server-side that uses to flow the authentication state to the client, which is then fixed for the lifetime of the WebAssembly application. :::moniker-end -For more information on (web) API calls using a service abstractions in Blazor Web Apps, see . +This app is a starting point for any OIDC authentication flow. OIDC is configured manually in the app and doesn't rely upon [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) or [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) packages, nor does the sample app require [Microsoft Azure](https://azure.microsoft.com/) hosting. However, the sample app can be used with Entra, Microsoft Identity Web, and hosted in Azure. -## OIDC provider terminology and guidance +Automatic non-interactive token refresh with the help of a custom cookie refresher (`CookieOidcRefresher.cs`). -Although you aren't required to adopt [Microsoft Entra (ME-ID)](https://www.microsoft.com/security/business/microsoft-entra) as the OIDC provider to use the sample app and the guidance in this article, this article describes settings for ME-ID using names that are found in Microsoft documentation and the Azure/Entra portals. OIDC settings have similar naming across OIDC providers. When using a third-party OIDC provider, use the provider's documentation in conjunction with the guidance in this article for app and web API registrations. +The [Backend for Frontend (BFF) pattern](/azure/architecture/patterns/backends-for-frontends) is adopted using [Aspire](/dotnet/aspire/get-started/aspire-overview) for service discovery and [YARP](https://dotnet.github.io/yarp/) for proxying requests to a weather forecast endpoint on the backend app. + +The backend web API (`MinimalApiJwt`) uses JWT-bearer authentication to validate JWT tokens saved by the Blazor Web App in the sign-in cookie. + +Aspire improves the experience of building .NET cloud-native apps. It provides a consistent, opinionated set of tools and patterns for building and running distributed apps. + +YARP (Yet Another Reverse Proxy) is a library used to create a reverse proxy server. `MapForwarder` in the `Program` file of the server project adds direct forwarding of HTTP requests that match the specified pattern to a specific destination using default configuration for the outgoing request, customized transforms, and default HTTP client: + +* When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` class to proxy the request for weather data with the user's access token. determines if an is available for use by the `GetWeatherForecastAsync` method. For more information, see . +* When the component is rendered on the client, the component uses the `ClientWeatherForecaster` service implementation, which uses a preconfigured (in the client project's `Program` file) to make a web API call to the server project. A Minimal API endpoint (`/weather-forecast`) defined in the server project's `Program` file transforms the request with the user's access token to obtain the weather data. + +For more information on (web) API calls using a service abstractions in Blazor Web Apps, see . ## Microsoft Entra ID app registrations @@ -116,9 +117,17 @@ dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SE If using Visual Studio, you can confirm the secret is set by right-clicking the server project in **Solution Explorer** and selecting **Manage User Secrets**. +## Aspire projects + +For more information on using Aspire and details on the `.AppHost` and `.ServiceDefaults` projects of the sample app, see the [Aspire documentation](/dotnet/aspire/). + +Confirm that you've met the prerequisites for Aspire. For more information, see the *Prerequisites* section of [Quickstart: Build your first Aspire solution](/dotnet/aspire/get-started/build-your-first-aspire-app?tabs=visual-studio#prerequisites). + +The sample app only configures an insecure HTTP launch profile (`http`) for use during development testing. For more information, including an example of insecure and secure launch settings profiles, see [Allow unsecure transport in Aspire (Aspire documentation)](/dotnet/aspire/troubleshooting/allow-unsecure-transport). + ## `MinimalApiJwt` project -The `MinimalApiJwt` project is a backend web API for multiple frontend projects. The project configures a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. +The `MinimalApiJwt` project is a backend web API for multiple frontend projects. The project configures a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. Requests from the Blazor Web App server-side project (`BlazorWebAppOidc`) are proxied to the `MinimalApiJwt` project. The `MinimalApiJwt.http` file can be used for testing the weather data request. Note that the `MinimalApiJwt` project must be running to test the endpoint, and the endpoint is hardcoded into the file. For more information, see . @@ -134,7 +143,7 @@ The project includes packages and configuration to produce [OpenAPI documents](x :::moniker-end -The project creates a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data: +A secure weather forecast data endpoint is in the project's `Program` file: ```csharp app.MapGet("/weather-forecast", () => @@ -151,6 +160,8 @@ app.MapGet("/weather-forecast", () => }).RequireAuthorization(); ``` +The extension method requires authorization for the route definition. For any controllers that you add to the project, add the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the controller or action. + Configure the project in the of the call in the project's `Program` file. The sets the Authority for making OIDC calls. We recommend using a separate app registration for the `MinimalApiJwt` project. The authority matches the issurer (`iss`) of the JWT returned by the identity provider. @@ -196,61 +207,11 @@ AAD B2C tenant App ID URI example: jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555"; ``` -## Blazor Web App server project (`BlazorWebAppOidc`) - -The `BlazorWebAppOidc` project is the server-side project of the Blazor Web App. - -A (`TokenHandler`) manages attaching a user's access token to outgoing requests. The token handler only executes during static server-side rendering (static SSR), so using is safe in this scenario. For more information, see and . - -`TokenHandler.cs`: - -```csharp -public class TokenHandler(IHttpContextAccessor httpContextAccessor) : - DelegatingHandler -{ - protected override async Task SendAsync( - HttpRequestMessage request, CancellationToken cancellationToken) - { - if (httpContextAccessor.HttpContext is null) - { - throw new Exception("HttpContext not available"); - } - - var accessToken = await httpContextAccessor.HttpContext - .GetTokenAsync("access_token"); - - request.Headers.Authorization = - new AuthenticationHeaderValue("Bearer", accessToken); - - return await base.SendAsync(request, cancellationToken); - } -} -``` - -In the project's `Program` file, the token handler (`TokenHandler`) is registered as a service and specified as the message handler with for making secure requests to the backend `MinimalApiJwt` web API using a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory) ("`ExternalApi`"). - -```csharp -builder.Services.AddScoped(); - -builder.Services.AddHttpClient("ExternalApi", - client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ?? - throw new Exception("Missing base address!"))) - .AddHttpMessageHandler(); -``` - -In the project's `appsettings.json` file, configure the external API URI: - -```json -"ExternalApiUri": "{BASE ADDRESS}" -``` - -Example: +## Server-side Blazor Web App project (`BlazorWebAppOidc`) -```json -"ExternalApiUri": "https://localhost:7277" -``` +This section explains how to configure the server-side Blazor project. -The following configuration is found in the project's `Program` file on the call to : +The following configuration is found in the project's `Program` file on the call to . :::moniker range=">= aspnetcore-9.0" @@ -286,6 +247,26 @@ Scope for offline access (): Configure the `Weather.Get` scope for accessing the external web API for weather data. In the following example, the `{APP ID URI}` placeholder is found in the Entra or Azure portal where the web API is exposed. For any other identity provider, use the appropriate scope. + +```csharp +oidcOptions.Scope.Add("{APP ID URI}/Weather.Get"); +``` + +The format of the scope depends on the type of tenant in use. In the following examples, the Tenant Domain is `contoso.onmicrosoft.com`, and the Client ID is `11112222-bbbb-3333-cccc-4444dddd5555`. + +ME-ID tenant App ID URI example: + +```csharp +oidcOptions.Scope.Add("api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"); +``` + +AAD B2C tenant App ID URI example: + +```csharp +oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"); +``` + and : Sets the Authority and Client ID for OIDC calls. ```csharp @@ -325,8 +306,6 @@ oidcOptions.TokenValidationParameters.RoleClaimType = "roles"; Path configuration: Paths must match the redirect URI (login callback path) and post logout redirect (signed-out callback path) paths configured when registering the application with the OIDC provider. In the Azure portal, paths are configured in the **Authentication** blade of the app's registration. Both the sign-in and sign-out paths must be registered as redirect URIs. The default values are `/signin-oidc` and `/signout-callback-oidc`. -: The request path within the app's base path where the user-agent is returned. - Configure the signed-out callback path in the app's OIDC provider registration. In the following example, the `{PORT}` placeholder is the app's port: > :::no-loc text="https://localhost:{PORT}/signin-oidc"::: @@ -334,7 +313,7 @@ Configure the signed-out callback path in the app's OIDC provider registration. > [!NOTE] > A port isn't required for `localhost` addresses when using Microsoft Entra ID. Most other OIDC providers require the correct port. - (configuration key: "`SignedOutCallbackPath`"): The request path within the app's base path intercepted by the OIDC handler where the user agent is first returned after signing out from the identity provider. The sample app doesn't set a value for the path because the default value of "`/signout-callback-oidc`" is used. After intercepting the request, the OIDC handler redirects to the or , if specified. + (configuration key: "`SignedOutCallbackPath`"): The request path within the app's base path intercepted by the OIDC handler where the user agent is first returned after signing out from the identity provider. The sample app doesn't set a value for the path because the default value of "`/signout-callback-oidc`" is used. After intercepting the request, the OIDC handler redirects to the or , if specified. Configure the signed-out callback path in the app's OIDC provider registration. In the following example, the `{PORT}` placeholder is the app's port: @@ -375,7 +354,7 @@ var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOpti oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate; ``` -## Blazor Web App client project (`BlazorWebAppOidc.Client`) +## Client-side Blazor Web App project (`BlazorWebAppOidc.Client`) The `BlazorWebAppOidc.Client` project is the client-side project of the Blazor Web App. @@ -397,7 +376,7 @@ The sample app only provides a user name and email for display purposes. :::zone-end -:::zone pivot="non-bff-pattern-server" +:::zone pivot="without-yarp-and-aspire" :::moniker range=">= aspnetcore-9.0" @@ -405,11 +384,12 @@ For Microsoft Entra ID or Azure AD B2C, you can use . For any controllers that you add to the project, add the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the controller or action. 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). + +* The app securely calls a web API for weather data: + + * When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` on the server to obtain weather data from the web API in the `MinimalApiJwt` project using a (`TokenHandler`) that attaches the access token from the to the request. + * When the component is rendered on the client, the component uses the `ClientWeatherForecaster` service implementation, which uses a preconfigured (in the client project's `Program` file) to make the web API call from the server project's `ServerWeatherForecaster`. + +:::moniker range=">= aspnetcore-9.0" + +* The server project calls to add a server-side authentication state provider that uses to flow the authentication state to the client. The client calls to deserialize and use the authentication state passed by the server. The authentication state is fixed for the lifetime of the WebAssembly application. + +:::moniker-end + +:::moniker range="< aspnetcore-9.0" + +* The `PersistingAuthenticationStateProvider` class (`PersistingAuthenticationStateProvider.cs`) is a server-side that uses to flow the authentication state to the client, which is then fixed for the lifetime of the WebAssembly application. + +:::moniker-end + +For more information on (web) API calls using a service abstractions in Blazor Web Apps, see . + +## OIDC provider terminology and guidance + +Although you aren't required to adopt [Microsoft Entra (ME-ID)](https://www.microsoft.com/security/business/microsoft-entra) as the OIDC provider to use the sample app and the guidance in this article, this article describes settings for ME-ID using names that are found in Microsoft documentation and the Azure/Entra portals. OIDC settings have similar naming across OIDC providers. When using a third-party OIDC provider, use the provider's documentation in conjunction with the guidance in this article for app and web API registrations. + ## Microsoft Entra ID app registrations -We recommend using separate registrations for apps and web APIs, even when the apps and web APIs are in the same solution. The following guidance is for the `BlazorWebAppOidcServer` app and `MinimalApiJwt` web API of the sample solution, but the same guidance applies generally to any Entra-based registrations for apps and web APIs. +We recommend using separate registrations for apps and web APIs, even when the apps and web APIs are in the same solution. The following guidance is for the `BlazorWebAppOidc` app and `MinimalApiJwt` web API of the sample solution, but the same guidance applies generally to any Entra-based registrations for apps and web APIs. + +For app and web API registration guidance, see [Register an application in Microsoft Entra ID](/entra/identity-platform/quickstart-register-app). Register the web API (`MinimalApiJwt`) first so that you can then grant access to the web API when registering the app. The web API's tenant ID and client ID are used to configure the web API in its `Program` file. After registering the web API, expose the web API in **App registrations** > **Expose an API** with a scope name of `Weather.Get`. Record the App ID URI for use in the app's configuration. -Next, register the app (`BlazorWebAppOidcServer`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. +Next, register the app (`BlazorWebAppOidc`/`BlazorWebApOidc.Client`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. In the Entra or Azure portal's **Implicit grant and hybrid flows** app registration configuration, don't select either checkbox for the authorization endpoint to return **Access tokens** or **ID tokens**. The OpenID Connect handler automatically requests the appropriate tokens using the code returned from the authorization endpoint. @@ -443,13 +455,13 @@ Additional Entra configuration guidance for specific settings is provided later ## Establish the client secret -*This section only applies to the server project of the Blazor Web App (`BlazorWebAppOidcServer` project).* +*This section only applies to the server project of the Blazor Web App (`BlazorWebAppOidc` project).* [!INCLUDE[](~/blazor/security/includes/secure-authentication-flows.md)] For local development testing, use the [Secret Manager tool](xref:security/app-secrets) to store the Blazor server project's client secret under the configuration key `Authentication:Schemes:MicrosoftOidc:ClientSecret`. -The Blazor server project hasn't been initialized for the Secret Manager tool. Use a command shell, such as the Developer PowerShell command shell in Visual Studio, to execute the following command. Before executing the command, change the directory with the `cd` command to the server project's directory. The command establishes a user secrets identifier (`` in the app's project file): +The Blazor server project hasn't been initialized for the Secret Manager tool. Use a command shell, such as the Developer PowerShell command shell in Visual Studio, to execute the following command. Before executing the command, change the directory with the `cd` command to the server project's directory. The command establishes a user secrets identifier (`` in the server app's project file): ```dotnetcli dotnet user-secrets init @@ -461,7 +473,7 @@ Execute the following command to set the client secret. The `{SECRET}` placehold dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}" ``` -If using Visual Studio, you can confirm the secret is set by right-clicking the project in **Solution Explorer** and selecting **Manage User Secrets**. +If using Visual Studio, you can confirm the secret is set by right-clicking the server project in **Solution Explorer** and selecting **Manage User Secrets**. ## `MinimalApiJwt` project @@ -543,9 +555,9 @@ AAD B2C tenant App ID URI example: jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555"; ``` -## `BlazorWebAppOidcServer` project +## Blazor Web App server project (`BlazorWebAppOidc`) -Automatic non-interactive token refresh is managed by a custom cookie refresher (`CookieOidcRefresher.cs`). +The `BlazorWebAppOidc` project is the server-side project of the Blazor Web App. A (`TokenHandler`) manages attaching a user's access token to outgoing requests. The token handler only executes during static server-side rendering (static SSR), so using is safe in this scenario. For more information, see and . @@ -585,20 +597,6 @@ builder.Services.AddHttpClient("ExternalApi", .AddHttpMessageHandler(); ``` -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). - -The `ExternalApi` HTTP client is used to make a request for weather data to the secure web API. In the [`OnInitializedAsync` lifecycle event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync) of `Weather.razor`: - -```csharp -using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast"); -var client = ClientFactory.CreateClient("ExternalApi"); -using var response = await client.SendAsync(request); -response.EnsureSuccessStatusCode(); - -forecasts = await response.Content.ReadFromJsonAsync() ?? - throw new IOException("No weather forecast!"); -``` - In the project's `appsettings.json` file, configure the external API URI: ```json @@ -635,26 +633,6 @@ Scopes for `openid` and `profile` (: Defines whether access and refresh tokens should be stored in the after a successful authorization. This property is set to `true` so the refresh token gets stored for non-interactive token refresh. ```csharp @@ -723,7 +701,7 @@ Configure the signed-out callback path in the app's OIDC provider registration. > [!NOTE] > When using Microsoft Entra ID, set the path in the **Web** platform configuration's **Redirect URI** entries in the Entra or Azure portal. A port isn't required for `localhost` addresses when using Entra. Most other OIDC providers require the correct port. If you don't add the signed-out callback path URI to the app's registration in Entra, Entra refuses to redirect the user back to the app and merely asks them to close their browser window. - + : Requests received on this path cause the handler to invoke sign-out using the sign-out scheme. In the following example, the `{PORT}` placeholder is the app's port: @@ -756,77 +734,65 @@ var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOpti oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate; ``` -:::zone-end +## Blazor Web App client project (`BlazorWebAppOidc.Client`) -:::zone pivot="bff-pattern" +The `BlazorWebAppOidc.Client` project is the client-side project of the Blazor Web App. :::moniker range=">= aspnetcore-9.0" -For Microsoft Entra ID or Azure AD B2C, you can use from [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) ([`Microsoft.Identity.Web` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web), [API documentation]()), which adds both the OIDC and Cookie authentication handlers with the appropriate defaults. The sample app and the guidance in this article don't use Microsoft Identity Web. The guidance demonstrates how to configure the OIDC handler *manually* for any OIDC provider. For more information on implementing Microsoft Identity Web, see . +The client calls to deserialize and use the authentication state passed by the server. The authentication state is fixed for the lifetime of the WebAssembly application. :::moniker-end -This version of the article covers implementing OIDC with the [Backend for Frontend (BFF) pattern](/azure/architecture/patterns/backends-for-frontends). If the app's specification doesn't call for adopting the BFF pattern, change the article version selector to **Non-BFF pattern (Interactive Auto)** (Interactive Auto rendering) or **Non-BFF pattern (Interactive Server)** (Interactive Server rendering). - -## Prerequisites - -[Aspire](/dotnet/aspire/get-started/aspire-overview) requires [Visual Studio](https://visualstudio.microsoft.com/) version 17.10 or later. - -Also, see the *Prerequisites* section of [Quickstart: Build your first Aspire solution](/dotnet/aspire/get-started/build-your-first-aspire-app?tabs=visual-studio#prerequisites). +:::moniker range="< aspnetcore-9.0" -## Sample solution +The `PersistentAuthenticationStateProvider` class (`PersistentAuthenticationStateProvider.cs`) is a client-side that determines the user's authentication state by looking for data persisted in the page when it was rendered on the server. The authentication state is fixed for the lifetime of the WebAssembly application. -The sample app consists of the following projects: +:::moniker-end -* Aspire: - * `Aspire.AppHost`: Used to manage the high-level orchestration concerns of the app. - * `Aspire.ServiceDefaults`: Contains default Aspire app configurations that can be extended and customized as needed. -* `MinimalApiJwt`: Backend web API, containing an example [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. -* `BlazorWebAppOidc`: Server-side project of the Blazor Web App. The project uses [YARP](https://dotnet.github.io/yarp/) to proxy requests to a weather forecast endpoint in the backend web API project (`MinimalApiJwt`) with the `access_token` stored in the authentication cookie. -* `BlazorWebAppOidc.Client`: Client-side project of the Blazor Web App. +If the user needs to log in or out, a full page reload is required. -Access the sample through the latest version folder in the Blazor samples repository with the following link. The sample is in the `BlazorWebAppOidcBff` folder for .NET 8 or later. +The sample app only provides a user name and email for display purposes. -[View or download sample code](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps)) +:::zone-end -The Blazor Web App uses [the Auto render mode with global interactivity](xref:blazor/components/render-modes). +:::zone pivot="without-yarp-and-aspire-server" :::moniker range=">= aspnetcore-9.0" -The server project calls to add a server-side authentication state provider that uses to flow the authentication state to the client. The client calls to deserialize and use the authentication state passed by the server. The authentication state is fixed for the lifetime of the WebAssembly application. +For Microsoft Entra ID or Azure AD B2C, you can use from [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) ([`Microsoft.Identity.Web` NuGet package](https://www.nuget.org/packages/Microsoft.Identity.Web), [API documentation]()), which adds both the OIDC and Cookie authentication handlers with the appropriate defaults. The sample app and the guidance in this article don't use Microsoft Identity Web. The guidance demonstrates how to configure the OIDC handler *manually* for any OIDC provider. For more information on implementing Microsoft Identity Web, see . :::moniker-end -:::moniker range="< aspnetcore-9.0" +This version of the article covers implementing OIDC with the [Backend for Frontend (BFF) pattern](/azure/architecture/patterns/backends-for-frontends) without [YARP](https://dotnet.github.io/yarp/) and [Aspire](/dotnet/aspire/get-started/aspire-overview). Change the article version selector to either **Without YARP and Aspire (Interactive Auto)** or **Without YARP and Aspire (Interactive Server)** if the app's specification doesn't call for adopting YARP and Aspire. -The `PersistingAuthenticationStateProvider` class (`PersistingAuthenticationStateProvider.cs`) is a server-side that uses to flow the authentication state to the client, which is then fixed for the lifetime of the WebAssembly application. - -:::moniker-end - -This app is a starting point for any OIDC authentication flow. OIDC is configured manually in the app and doesn't rely upon [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) or [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) packages, nor does the sample app require [Microsoft Azure](https://azure.microsoft.com/) hosting. However, the sample app can be used with Entra, Microsoft Identity Web, and hosted in Azure. +The following specification is adopted: -Automatic non-interactive token refresh with the help of a custom cookie refresher (`CookieOidcRefresher.cs`). +* The Blazor Web App uses [the Server render mode with global interactivity](xref:blazor/components/render-modes). +* This app is a starting point for any OIDC authentication flow. OIDC is configured manually in the app and doesn't rely upon [Microsoft Entra ID](https://www.microsoft.com/security/business/microsoft-entra) or [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/) packages, nor does the sample app require [Microsoft Azure](https://azure.microsoft.com/) hosting. However, the sample app can be used with Entra, Microsoft Identity Web, and hosted in Azure. +* Automatic non-interactive token refresh. +* A separate web API project demonstrates a secure web API call for weather data. -The [Backend for Frontend (BFF) pattern](/azure/architecture/patterns/backends-for-frontends) is adopted using [Aspire](/dotnet/aspire/get-started/aspire-overview) for service discovery and [YARP](https://dotnet.github.io/yarp/) for proxying requests to a weather forecast endpoint on the backend app. +For an alternative experience using [Microsoft Authentication Library for .NET](/entra/msal/dotnet/), [Microsoft Identity Web](/entra/msal/dotnet/microsoft-identity-web/), and [Microsoft Entra ID](https://www.microsoft.com/security/business/identity-access/microsoft-entra-id), see . -The backend web API (`MinimalApiJwt`) uses JWT-bearer authentication to validate JWT tokens saved by the Blazor Web App in the sign-in cookie. +## Sample solution -Aspire improves the experience of building .NET cloud-native apps. It provides a consistent, opinionated set of tools and patterns for building and running distributed apps. +The sample app consists of the following projects: -YARP (Yet Another Reverse Proxy) is a library used to create a reverse proxy server. `MapForwarder` in the `Program` file of the server project adds direct forwarding of HTTP requests that match the specified pattern to a specific destination using default configuration for the outgoing request, customized transforms, and default HTTP client: +* `BlazorWebAppOidcServer`: Blazor Web App server-side project (global Interactive Server rendering). +* `MinimalApiJwt`: Backend web API with a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. -* When rendering the `Weather` component on the server, the component uses the `ServerWeatherForecaster` class to proxy the request for weather data with the user's access token. determines if an is available for use by the `GetWeatherForecastAsync` method. For more information, see . -* When the component is rendered on the client, the component uses the `ClientWeatherForecaster` service implementation, which uses a preconfigured (in the client project's `Program` file) to make a web API call to the server project. A Minimal API endpoint (`/weather-forecast`) defined in the server project's `Program` file transforms the request with the user's access token to obtain the weather data. +Access the sample through the latest version folder in the Blazor samples repository with the following link. The sample is in the `BlazorWebAppOidcBffServer` folder for .NET 8 or later. -For more information on (web) API calls using a service abstractions in Blazor Web Apps, see . +[View or download sample code](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps)) ## Microsoft Entra ID app registrations -We recommend using separate registrations for apps and web APIs, even when the apps and web APIs are in the same solution. The following guidance is for the `BlazorWebAppOidc` app and `MinimalApiJwt` web API of the sample solution, but the same guidance applies generally to any Entra-based registrations for apps and web APIs. +We recommend using separate registrations for apps and web APIs, even when the apps and web APIs are in the same solution. The following guidance is for the `BlazorWebAppOidcServer` app and `MinimalApiJwt` web API of the sample solution, but the same guidance applies generally to any Entra-based registrations for apps and web APIs. Register the web API (`MinimalApiJwt`) first so that you can then grant access to the web API when registering the app. The web API's tenant ID and client ID are used to configure the web API in its `Program` file. After registering the web API, expose the web API in **App registrations** > **Expose an API** with a scope name of `Weather.Get`. Record the App ID URI for use in the app's configuration. -Next, register the app (`BlazorWebAppOidc`/`BlazorWebApOidc.Client`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. +Next, register the app (`BlazorWebAppOidcServer`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. In the Entra or Azure portal's **Implicit grant and hybrid flows** app registration configuration, don't select either checkbox for the authorization endpoint to return **Access tokens** or **ID tokens**. The OpenID Connect handler automatically requests the appropriate tokens using the code returned from the authorization endpoint. @@ -836,13 +802,13 @@ Additional Entra configuration guidance for specific settings is provided later ## Establish the client secret -*This section only applies to the server project of the Blazor Web App (`BlazorWebAppOidc` project).* +*This section only applies to the server project of the Blazor Web App (`BlazorWebAppOidcServer` project).* [!INCLUDE[](~/blazor/security/includes/secure-authentication-flows.md)] For local development testing, use the [Secret Manager tool](xref:security/app-secrets) to store the Blazor server project's client secret under the configuration key `Authentication:Schemes:MicrosoftOidc:ClientSecret`. -The Blazor server project hasn't been initialized for the Secret Manager tool. Use a command shell, such as the Developer PowerShell command shell in Visual Studio, to execute the following command. Before executing the command, change the directory with the `cd` command to the server project's directory. The command establishes a user secrets identifier (`` in the server app's project file): +The Blazor server project hasn't been initialized for the Secret Manager tool. Use a command shell, such as the Developer PowerShell command shell in Visual Studio, to execute the following command. Before executing the command, change the directory with the `cd` command to the server project's directory. The command establishes a user secrets identifier (`` in the app's project file): ```dotnetcli dotnet user-secrets init @@ -854,19 +820,11 @@ Execute the following command to set the client secret. The `{SECRET}` placehold dotnet user-secrets set "Authentication:Schemes:MicrosoftOidc:ClientSecret" "{SECRET}" ``` -If using Visual Studio, you can confirm the secret is set by right-clicking the server project in **Solution Explorer** and selecting **Manage User Secrets**. - -## Aspire projects - -For more information on using Aspire and details on the `.AppHost` and `.ServiceDefaults` projects of the sample app, see the [Aspire documentation](/dotnet/aspire/). - -Confirm that you've met the prerequisites for Aspire. For more information, see the *Prerequisites* section of [Quickstart: Build your first Aspire solution](/dotnet/aspire/get-started/build-your-first-aspire-app?tabs=visual-studio#prerequisites). - -The sample app only configures an insecure HTTP launch profile (`http`) for use during development testing. For more information, including an example of insecure and secure launch settings profiles, see [Allow unsecure transport in Aspire (Aspire documentation)](/dotnet/aspire/troubleshooting/allow-unsecure-transport). +If using Visual Studio, you can confirm the secret is set by right-clicking the project in **Solution Explorer** and selecting **Manage User Secrets**. ## `MinimalApiJwt` project -The `MinimalApiJwt` project is a backend web API for multiple frontend projects. The project configures a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. Requests from the Blazor Web App server-side project (`BlazorWebAppOidc`) are proxied to the `MinimalApiJwt` project. +The `MinimalApiJwt` project is a backend web API for multiple frontend projects. The project configures a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data. The `MinimalApiJwt.http` file can be used for testing the weather data request. Note that the `MinimalApiJwt` project must be running to test the endpoint, and the endpoint is hardcoded into the file. For more information, see . @@ -882,7 +840,7 @@ The project includes packages and configuration to produce [OpenAPI documents](x :::moniker-end -A secure weather forecast data endpoint is in the project's `Program` file: +The project creates a [Minimal API](xref:fundamentals/minimal-apis) endpoint for weather data: ```csharp app.MapGet("/weather-forecast", () => @@ -899,8 +857,6 @@ app.MapGet("/weather-forecast", () => }).RequireAuthorization(); ``` -The extension method requires authorization for the route definition. For any controllers that you add to the project, add the [`[Authorize]` attribute](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) to the controller or action. - Configure the project in the of the call in the project's `Program` file. The sets the Authority for making OIDC calls. We recommend using a separate app registration for the `MinimalApiJwt` project. The authority matches the issurer (`iss`) of the JWT returned by the identity provider. @@ -946,11 +902,75 @@ AAD B2C tenant App ID URI example: jwtOptions.Audience = "https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555"; ``` -## Server-side Blazor Web App project (`BlazorWebAppOidc`) +## `BlazorWebAppOidcServer` project -This section explains how to configure the server-side Blazor project. +Automatic non-interactive token refresh is managed by a custom cookie refresher (`CookieOidcRefresher.cs`). -The following configuration is found in the project's `Program` file on the call to . +A (`TokenHandler`) manages attaching a user's access token to outgoing requests. The token handler only executes during static server-side rendering (static SSR), so using is safe in this scenario. For more information, see and . + +`TokenHandler.cs`: + +```csharp +public class TokenHandler(IHttpContextAccessor httpContextAccessor) : + DelegatingHandler +{ + protected override async Task SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) + { + if (httpContextAccessor.HttpContext is null) + { + throw new Exception("HttpContext not available"); + } + + var accessToken = await httpContextAccessor.HttpContext + .GetTokenAsync("access_token"); + + request.Headers.Authorization = + new AuthenticationHeaderValue("Bearer", accessToken); + + return await base.SendAsync(request, cancellationToken); + } +} +``` + +In the project's `Program` file, the token handler (`TokenHandler`) is registered as a service and specified as the message handler with for making secure requests to the backend `MinimalApiJwt` web API using a [named HTTP client](xref:blazor/call-web-api#named-httpclient-with-ihttpclientfactory) ("`ExternalApi`"). + +```csharp +builder.Services.AddScoped(); + +builder.Services.AddHttpClient("ExternalApi", + client => client.BaseAddress = new Uri(builder.Configuration["ExternalApiUri"] ?? + throw new Exception("Missing base address!"))) + .AddHttpMessageHandler(); +``` + +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). + +The `ExternalApi` HTTP client is used to make a request for weather data to the secure web API. In the [`OnInitializedAsync` lifecycle event](xref:blazor/components/lifecycle#component-initialization-oninitializedasync) of `Weather.razor`: + +```csharp +using var request = new HttpRequestMessage(HttpMethod.Get, "/weather-forecast"); +var client = ClientFactory.CreateClient("ExternalApi"); +using var response = await client.SendAsync(request); +response.EnsureSuccessStatusCode(); + +forecasts = await response.Content.ReadFromJsonAsync() ?? + throw new IOException("No weather forecast!"); +``` + +In the project's `appsettings.json` file, configure the external API URI: + +```json +"ExternalApiUri": "{BASE ADDRESS}" +``` + +Example: + +```json +"ExternalApiUri": "https://localhost:7277" +``` + +The following configuration is found in the project's `Program` file on the call to : :::moniker range=">= aspnetcore-9.0" @@ -974,36 +994,36 @@ Scopes for `openid` and `profile` (: Defines whether access and refresh tokens should be stored in the after a successful authorization. The value is set to `true` to authenticate requests for weather data from the backend web API project (`MinimalApiJwt`). +Configure the `Weather.Get` scope for accessing the external web API for weather data. The following example is based on using Entra ID in an ME-ID tenant domain. In the following example, the `{APP ID URI}` placeholder is found in the Entra or Azure portal where the web API is exposed. For any other identity provider, use the appropriate scope. ```csharp -oidcOptions.SaveTokens = true; +oidcOptions.Scope.Add("{APP ID URI}/Weather.Get"); ``` -Scope for offline access (): The `offline_access` scope is required for the refresh token. +The format of the scope depends on the type of tenant in use. In the following examples, the Tenant Domain is `contoso.onmicrosoft.com`, and the Client ID is `11112222-bbbb-3333-cccc-4444dddd5555`. + +ME-ID tenant App ID URI example: ```csharp -oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess); +oidcOptions.Scope.Add("api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"); ``` -Scopes for obtaining weather data from the web API (): Configure the `Weather.Get` scope for accessing the external web API for weather data. In the following example, the `{APP ID URI}` placeholder is found in the Entra or Azure portal where the web API is exposed. For any other identity provider, use the appropriate scope. +AAD B2C tenant App ID URI example: ```csharp -oidcOptions.Scope.Add("{APP ID URI}/Weather.Get"); +oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"); ``` -The format of the scope depends on the type of tenant in use. In the following examples, the Tenant Domain is `contoso.onmicrosoft.com`, and the Client ID is `11112222-bbbb-3333-cccc-4444dddd5555`. - -ME-ID tenant App ID URI example: +: Defines whether access and refresh tokens should be stored in the after a successful authorization. This property is set to `true` so the refresh token gets stored for non-interactive token refresh. ```csharp -oidcOptions.Scope.Add("api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"); +oidcOptions.SaveTokens = true; ``` -AAD B2C tenant App ID URI example: +Scope for offline access (): The `offline_access` scope is required for the refresh token. ```csharp -oidcOptions.Scope.Add("https://contoso.onmicrosoft.com/11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get"); +oidcOptions.Scope.Add(OpenIdConnectScope.OfflineAccess); ``` and : Sets the Authority and Client ID for OIDC calls. @@ -1032,7 +1052,7 @@ oidcOptions.Authority = "https://login.microsoftonline.com/common/v2.0"; oidcOptions.ResponseType = OpenIdConnectResponseType.Code; ``` - and configuration of and : Many OIDC servers use "`name`" and "`role`" rather than the SOAP/WS-Fed defaults in . When is set to `false`, the handler doesn't perform claims mappings and the claim names from the JWT are used directly by the app. The following example sets the role claim type to "`roles`," which is appropriate for [Microsoft Entra ID (ME-ID)](https://www.microsoft.com/security/business/microsoft-entra). Consult your identity provider's documentation for more information. + and configuration of and : Many OIDC servers use "`name`" and "`role`" rather than the SOAP/WS-Fed defaults in . When is set to `false`, the handler doesn't perform claims mappings, and the claim names from the JWT are used directly by the app. The following example sets the role claim type to "`roles`," which is appropriate for [Microsoft Entra ID (ME-ID)](https://www.microsoft.com/security/business/microsoft-entra). Consult your identity provider's documentation for more information. > [!NOTE] > must be set to `false` for most OIDC providers, which prevents renaming claims. @@ -1045,6 +1065,8 @@ oidcOptions.TokenValidationParameters.RoleClaimType = "roles"; Path configuration: Paths must match the redirect URI (login callback path) and post logout redirect (signed-out callback path) paths configured when registering the application with the OIDC provider. In the Azure portal, paths are configured in the **Authentication** blade of the app's registration. Both the sign-in and sign-out paths must be registered as redirect URIs. The default values are `/signin-oidc` and `/signout-callback-oidc`. +: The request path within the app's base path where the user-agent is returned. + Configure the signed-out callback path in the app's OIDC provider registration. In the following example, the `{PORT}` placeholder is the app's port: > :::no-loc text="https://localhost:{PORT}/signin-oidc"::: @@ -1052,7 +1074,7 @@ Configure the signed-out callback path in the app's OIDC provider registration. > [!NOTE] > A port isn't required for `localhost` addresses when using Microsoft Entra ID. Most other OIDC providers require the correct port. - (configuration key: "`SignedOutCallbackPath`"): The request path within the app's base path intercepted by the OIDC handler where the user agent is first returned after signing out from the identity provider. The sample app doesn't set a value for the path because the default value of "`/signout-callback-oidc`" is used. After intercepting the request, the OIDC handler redirects to the or , if specified. + (configuration key: "`SignedOutCallbackPath`"): The request path within the app's base path intercepted by the OIDC handler where the user agent is first returned after signing out from the identity provider. The sample app doesn't set a value for the path because the default value of "`/signout-callback-oidc`" is used. After intercepting the request, the OIDC handler redirects to the or , if specified. Configure the signed-out callback path in the app's OIDC provider registration. In the following example, the `{PORT}` placeholder is the app's port: @@ -1060,7 +1082,7 @@ Configure the signed-out callback path in the app's OIDC provider registration. > [!NOTE] > When using Microsoft Entra ID, set the path in the **Web** platform configuration's **Redirect URI** entries in the Entra or Azure portal. A port isn't required for `localhost` addresses when using Entra. Most other OIDC providers require the correct port. If you don't add the signed-out callback path URI to the app's registration in Entra, Entra refuses to redirect the user back to the app and merely asks them to close their browser window. - + : Requests received on this path cause the handler to invoke sign-out using the sign-out scheme. In the following example, the `{PORT}` placeholder is the app's port: @@ -1093,33 +1115,13 @@ var microsoftIssuerValidator = AadIssuerValidator.GetAadIssuerValidator(oidcOpti oidcOptions.TokenValidationParameters.IssuerValidator = microsoftIssuerValidator.Validate; ``` -## Client-side Blazor Web App project (`BlazorWebAppOidc.Client`) - -The `BlazorWebAppOidc.Client` project is the client-side project of the Blazor Web App. - -:::moniker range=">= aspnetcore-9.0" - -The client calls to deserialize and use the authentication state passed by the server. The authentication state is fixed for the lifetime of the WebAssembly application. - -:::moniker-end - -:::moniker range="< aspnetcore-9.0" - -The `PersistentAuthenticationStateProvider` class (`PersistentAuthenticationStateProvider.cs`) is a client-side that determines the user's authentication state by looking for data persisted in the page when it was rendered on the server. The authentication state is fixed for the lifetime of the WebAssembly application. - -:::moniker-end - -If the user needs to log in or out, a full page reload is required. - -The sample app only provides a user name and email for display purposes. - :::zone-end :::moniker range=">= aspnetcore-9.0" ## Only serialize the name and role claims -*This section only applies to the non-BFF pattern (Interactive Auto) and BFF pattern (Interactive Auto) and their sample apps.* +*This section only applies to the Interactive Auto render mode sample apps.* In the `Program` file, all claims are serialized by setting to `true`. If you only want the name and role claims serialized for CSR, remove the option or set it to `false`. @@ -1230,7 +1232,7 @@ For more information on configuration, see the following resources: You can use the "common" Authority for single-tenant apps, but you must take the following steps to implement a custom issuer validator. -Add the [`Microsoft.IdentityModel.Validators` NuGet package](https://www.nuget.org/packages/Microsoft.IdentityModel.Validators) to the `BlazorWebAppOidc`, `BlazorWebAppOidcServer`, or `BlazorWebAppOidcBff` project. +Add the [`Microsoft.IdentityModel.Validators` NuGet package](https://www.nuget.org/packages/Microsoft.IdentityModel.Validators) to the `BlazorWebAppOidcBffAutoYarpAspire`, `BlazorWebAppOidcBffAuto`, or `BlazorWebAppOidcBffServer` project. [!INCLUDE[](~/includes/package-reference.md)] diff --git a/aspnetcore/security/authentication/configure-jwt-bearer-authentication.md b/aspnetcore/security/authentication/configure-jwt-bearer-authentication.md index d53591fe172f..ee30f5e5f19c 100644 --- a/aspnetcore/security/authentication/configure-jwt-bearer-authentication.md +++ b/aspnetcore/security/authentication/configure-jwt-bearer-authentication.md @@ -265,7 +265,7 @@ This is easy to implement but the client application has full application access When using access tokens in a client application, the access tokens must be rotated, persisted, and stored on the server. In a web app, cookies are used to secure the session and can be used to store tokens via the property. - doesn't refresh access tokens automatically, but this functionality is planned for a future release. In the meantime, you can manually refresh the access token as [demonstrated in the Blazor Web App with OIDC documentation](/aspnet/core/blazor/security/blazor-web-app-with-oidc?pivots=with-bff-pattern#token-refresh) or use a third-party NuGet package, such as [`Duende.AccessTokenManagement.OpenIdConnect`](https://www.nuget.org/packages/Duende.AccessTokenManagement.OpenIdConnect). For more information, see [Duende token management](https://docs.duendesoftware.com/identityserver/v7/quickstarts/3a_token_management/). + doesn't refresh access tokens automatically, but this functionality is planned for a future release. In the meantime, you can manually refresh the access token as [demonstrated in the Blazor Web App with OIDC documentation](/aspnet/core/blazor/security/blazor-web-app-with-oidc?pivots=with-yarp-and-aspire#token-refresh) or use a third-party NuGet package, such as [`Duende.AccessTokenManagement.OpenIdConnect`](https://www.nuget.org/packages/Duende.AccessTokenManagement.OpenIdConnect). For more information, see [Duende token management](https://docs.duendesoftware.com/identityserver/v7/quickstarts/3a_token_management/). > [!NOTE] > If deploying to production, the cache should work in a multi-instance deployment. A persistent cache is normally required. @@ -288,13 +288,13 @@ Some secure token servers encrypt the access tokens. Access tokens do not requir For Blazor examples that use YARP to implement the BFF pattern, see the following articles: * -* +* :::moniker-end :::moniker range="< aspnetcore-9.0" -For a Blazor example that uses YARP to implement the BFF pattern, see . +For a Blazor example that uses YARP to implement the BFF pattern, see . :::moniker-end diff --git a/aspnetcore/zone-pivot-groups.yml b/aspnetcore/zone-pivot-groups.yml index 6997c3588a16..5ceac3a3b7dd 100644 --- a/aspnetcore/zone-pivot-groups.yml +++ b/aspnetcore/zone-pivot-groups.yml @@ -84,12 +84,12 @@ groups: title: Specification prompt: Choose the app specification pivots: - - id: non-bff-pattern - title: Non-BFF pattern (Interactive Auto) - - id: non-bff-pattern-server - title: Non-BFF pattern (Interactive Server) - - id: bff-pattern - title: BFF pattern (Interactive Auto) + - id: with-yarp-and-aspire + title: With YARP and Aspire (Interactive Auto) + - id: without-yarp-and-aspire + title: Without YARP and Aspire (Interactive Auto) + - id: without-yarp-and-aspire-server + title: Without YARP and Aspire (Interactive Server) - id: tooling title: Tooling prompt: Select your tooling From b4656568fcb3a43e049e4c84b81551a18497b5f6 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:15:27 -0500 Subject: [PATCH 2/3] Updates --- aspnetcore/blazor/security/blazor-web-app-with-oidc.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md index a57009104df3..1df60d3580ef 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md @@ -23,7 +23,7 @@ For Microsoft Entra ID or Azure AD B2C, you can use **Expose an API** with a scope name of `Weather.Get`. Record the App ID URI for use in the app's configuration. -Next, register the app (`BlazorWebAppOidc`/`BlazorWebApOidc.Client`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. +Next, register the app (`BlazorWebAppOidc`/`BlazorWebAppOidc.Client`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. In the Entra or Azure portal's **Implicit grant and hybrid flows** app registration configuration, don't select either checkbox for the authorization endpoint to return **Access tokens** or **ID tokens**. The OpenID Connect handler automatically requests the appropriate tokens using the code returned from the authorization endpoint. @@ -384,7 +384,7 @@ For Microsoft Entra ID or Azure AD B2C, you can use **Expose an API** with a scope name of `Weather.Get`. Record the App ID URI for use in the app's configuration. -Next, register the app (`BlazorWebAppOidc`/`BlazorWebApOidc.Client`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. +Next, register the app (`BlazorWebAppOidc`/`BlazorWebAppOidc.Client`) with a **Web** platform configuration and a **Redirect URI** of `https://localhost/signin-oidc` (a port isn't required). The app's tenant ID and client ID, along with the web API's base address, App ID URI, and weather scope name, are used to configure the app in its `Program` file. Grant API permission to access the web API in **App registrations** > **API permissions**. If the app's security specification calls for it, you can grant admin consent for the organization to access the web API. Authorized users and groups are assigned to the app's registration in **App registrations** > **Enterprise applications**. In the Entra or Azure portal's **Implicit grant and hybrid flows** app registration configuration, don't select either checkbox for the authorization endpoint to return **Access tokens** or **ID tokens**. The OpenID Connect handler automatically requests the appropriate tokens using the code returned from the authorization endpoint. @@ -764,7 +764,7 @@ For Microsoft Entra ID or Azure AD B2C, you can use Date: Thu, 18 Dec 2025 09:22:34 -0500 Subject: [PATCH 3/3] Updates --- aspnetcore/blazor/security/blazor-web-app-with-oidc.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md index 1df60d3580ef..0ac7cabad9b5 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-oidc.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-oidc.md @@ -1232,7 +1232,7 @@ For more information on configuration, see the following resources: You can use the "common" Authority for single-tenant apps, but you must take the following steps to implement a custom issuer validator. -Add the [`Microsoft.IdentityModel.Validators` NuGet package](https://www.nuget.org/packages/Microsoft.IdentityModel.Validators) to the `BlazorWebAppOidcBffAutoYarpAspire`, `BlazorWebAppOidcBffAuto`, or `BlazorWebAppOidcBffServer` project. +Add the [`Microsoft.IdentityModel.Validators` NuGet package](https://www.nuget.org/packages/Microsoft.IdentityModel.Validators) to the server project. [!INCLUDE[](~/includes/package-reference.md)]