diff --git a/aspnetcore/blazor/globalization-localization.md b/aspnetcore/blazor/globalization-localization.md index 669b6df1987d..af9c1898fa3f 100644 --- a/aspnetcore/blazor/globalization-localization.md +++ b/aspnetcore/blazor/globalization-localization.md @@ -496,6 +496,8 @@ Use the `CultureExample1` component shown in the [Demonstration component](#demo ## Dynamically set the server-side culture by user preference + + :::moniker range=">= aspnetcore-8.0" Examples of locations where an app might store a user's preference include in [browser local storage](https://developer.mozilla.org/docs/Web/API/Window/localStorage) (common for client-side rendering), in a localization cookie or database (common for server-side rendering), or in an external service attached to an external database and accessed by a [web API](xref:blazor/call-web-api). diff --git a/aspnetcore/blazor/host-and-deploy/server.md b/aspnetcore/blazor/host-and-deploy/server.md index ffff9190dd78..deb6cbad37ec 100644 --- a/aspnetcore/blazor/host-and-deploy/server.md +++ b/aspnetcore/blazor/host-and-deploy/server.md @@ -354,7 +354,6 @@ In the following example: * The app listens for traffic on port 5000. ``` -ProxyRequests On ProxyPreserveHost On ProxyPassMatch ^/_blazor/(.*) http://localhost:5000/_blazor/$1 ProxyPass /_blazor ws://localhost:5000/_blazor diff --git a/aspnetcore/blazor/host-and-deploy/webassembly.md b/aspnetcore/blazor/host-and-deploy/webassembly.md index b6868ca9cb09..991833cdf616 100644 --- a/aspnetcore/blazor/host-and-deploy/webassembly.md +++ b/aspnetcore/blazor/host-and-deploy/webassembly.md @@ -759,7 +759,6 @@ The following example hosts the app at a root URL (no sub-app path): - ProxyRequests On ProxyPreserveHost On ProxyPass / http://localhost:5000/ ProxyPassReverse / http://localhost:5000/ @@ -780,7 +779,6 @@ To configure the server to host the app at a sub-app path, the `{PATH}` placehol - ProxyRequests On ProxyPreserveHost On ProxyPass / http://localhost:5000/{PATH} ProxyPassReverse / http://localhost:5000/{PATH} @@ -801,7 +799,6 @@ For an app that responds to requests at `/blazor`: - ProxyRequests On ProxyPreserveHost On ProxyPass / http://localhost:5000/blazor ProxyPassReverse / http://localhost:5000/blazor diff --git a/aspnetcore/blazor/includes/location-client-and-server-net-6-or-later.md b/aspnetcore/blazor/includes/location-client-and-server-net-6-or-later.md deleted file mode 100644 index d45e0205c842..000000000000 --- a/aspnetcore/blazor/includes/location-client-and-server-net-6-or-later.md +++ /dev/null @@ -1,40 +0,0 @@ -Throughout this article, the terms **client**/**client-side** and **server**/**server-side** are used to distinguish locations where app code executes: - -:::moniker range=">= aspnetcore-8.0" - -* **Client**/**client-side** - * Client-side rendering (CSR) and interactivity of a Blazor Web App. The `Program` file is `Program.cs` of the client project (`BlazorWeb-CSharp.Client`). Blazor script start configuration is found in the `App` component (`Components/App.razor`) of the server project (`BlazorWeb-CSharp`). - * A Blazor WebAssembly app. The `Program` file is `Program.cs`. Blazor script start configuration is found in the `wwwroot/index.html` file. -* **Server**/**server-side**: Server-side rendering (SSR) and interactivity of a Blazor Web App. The `Program` file is `Program.cs` of the server project (`BlazorWeb-CSharp`). Blazor script start configuration is found in the `App` component (`Components/App.razor`). - -Routable components with an `@page` directive are placed in the `Components/Pages` folder. Non-routable shared components are placed in the `Components` folder. - -:::moniker-end - -:::moniker range=">= aspnetcore-7.0 < aspnetcore-8.0" - -* **Client**/**client-side** - * The **`Client`** project of a hosted Blazor WebAssembly app. - * A Blazor WebAssembly app. - * Blazor script start configuration is found in the `wwwroot/index.html` file. - * The `Program` file is `Program.cs`. -* **Server**/**server-side** - * The **`Server`** project of a hosted Blazor WebAssembly app. - * A Blazor Server app. Blazor script start configuration is found in `Pages/_Host.cshtml`. - * The `Program` file is `Program.cs`. - -:::moniker-end - -:::moniker range="< aspnetcore-7.0" - -* **Client**/**client-side** - * The **`Client`** project of a hosted Blazor WebAssembly app. - * A Blazor WebAssembly app. - * Blazor script start configuration is found in the `wwwroot/index.html` file. - * The `Program` file is `Program.cs`. -* **Server**/**server-side** - * The **`Server`** project of a hosted Blazor WebAssembly app. - * A Blazor Server app. Blazor script start configuration is found in `Pages/_Layout.cshtml`. - * The `Program` file is `Program.cs`. - -:::moniker-end diff --git a/aspnetcore/blazor/includes/location-client-and-server-net-7-or-later.md b/aspnetcore/blazor/includes/location-client-and-server-net-7-or-later.md deleted file mode 100644 index 552544bb65ec..000000000000 --- a/aspnetcore/blazor/includes/location-client-and-server-net-7-or-later.md +++ /dev/null @@ -1,26 +0,0 @@ -Throughout this article, the terms **client**/**client-side** and **server**/**server-side** are used to distinguish locations where app code executes: - -:::moniker range=">= aspnetcore-8.0" - -* **Client**/**client-side** - * Client-side rendering (CSR) and interactivity of a Blazor Web App. The `Program` file is `Program.cs` of the client project (`BlazorWeb-CSharp.Client`). Blazor script start configuration is found in the `App` component (`Components/App.razor`) of the server project (`BlazorWeb-CSharp`). - * A Blazor WebAssembly app. The `Program` file is `Program.cs`. Blazor script start configuration is found in the `wwwroot/index.html` file. -* **Server**/**server-side**: Server-side rendering (SSR) and interactivity of a Blazor Web App. The `Program` file is `Program.cs` of the server project (`BlazorWeb-CSharp`). Blazor script start configuration is found in the `App` component (`Components/App.razor`). - -Routable components with an `@page` directive are placed in the `Components/Pages` folder. Non-routable shared components are placed in the `Components` folder. - -:::moniker-end - -:::moniker range="< aspnetcore-8.0" - -* **Client**/**client-side** - * The **`Client`** project of a hosted Blazor WebAssembly app. - * A Blazor WebAssembly app. - * Blazor script start configuration is found in the `wwwroot/index.html` file. - * The `Program` file is `Program.cs`. -* **Server**/**server-side** - * The **`Server`** project of a hosted Blazor WebAssembly app. - * A Blazor Server app. Blazor script start configuration is found in `Pages/_Host.cshtml`. - * The `Program` file is `Program.cs`. - -:::moniker-end diff --git a/aspnetcore/blazor/includes/location-client-and-server-net-8-or-later.md b/aspnetcore/blazor/includes/location-client-and-server-net-8-or-later.md deleted file mode 100644 index ef77fa700673..000000000000 --- a/aspnetcore/blazor/includes/location-client-and-server-net-8-or-later.md +++ /dev/null @@ -1,8 +0,0 @@ -Throughout this article, the terms **client**/**client-side** and **server**/**server-side** are used to distinguish locations where app code executes: - -* **Client**/**client-side** - * Client-side rendering (CSR) and interactivity of a Blazor Web App. The `Program` file is `Program.cs` of the client project (`BlazorWeb-CSharp.Client`). Blazor script start configuration is found in the `App` component (`Components/App.razor`) of the server project (`BlazorWeb-CSharp`). - * A Blazor WebAssembly app. The `Program` file is `Program.cs`. Blazor script start configuration is found in the `wwwroot/index.html` file. -* **Server**/**server-side**: Server-side rendering (SSR) and interactivity of a Blazor Web App. The `Program` file is `Program.cs` of the server project (`BlazorWeb-CSharp`). Blazor script start configuration is found in the `App` component (`Components/App.razor`). - -Routable components with an `@page` directive are placed in the `Components/Pages` folder. Non-routable shared components are placed in the `Components` folder. diff --git a/aspnetcore/blazor/security/content-security-policy.md b/aspnetcore/blazor/security/content-security-policy.md index 0f9caf724cca..197bf61a0d01 100644 --- a/aspnetcore/blazor/security/content-security-policy.md +++ b/aspnetcore/blazor/security/content-security-policy.md @@ -14,6 +14,8 @@ uid: blazor/security/content-security-policy This article explains how to use a [Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP) with ASP.NET Core Blazor apps to help protect against [Cross-Site Scripting (XSS)](xref:security/cross-site-scripting) attacks. +[!INCLUDE[](~/blazor/includes/location-client-and-server-net31-or-later.md)] + [Cross-Site Scripting (XSS)](xref:security/cross-site-scripting) is a security vulnerability where an attacker places one or more malicious client-side scripts into an app's rendered content. A CSP helps protect against XSS attacks by informing the browser of valid: * Sources for loaded content, including scripts, stylesheets, images, and plugins. @@ -27,7 +29,7 @@ CSP is supported in most modern desktop and mobile browsers, including Chrome, E ## Policy directives -Minimally, specify the following directives and sources for Blazor apps. Add additional directives and sources as needed. The following directives are used in the *Apply the policy* section of this article, where example security policies for Blazor WebAssembly and Blazor Server are provided: +Minimally, specify the following directives and sources for Blazor apps. Add additional directives and sources as needed. The following directives are used in the *Apply the policy* section of this article, where example security policies for Blazor apps are provided: :::moniker range=">= aspnetcore-8.0" @@ -39,10 +41,10 @@ Minimally, specify the following directives and sources for Blazor apps. Add add * [object-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/object-src): Indicates valid sources for the ``, ``, and `` tags. Specify `none` to prevent all URL sources. * [script-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/script-src): Indicates valid sources for scripts. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. - * In a Blazor WebAssembly app: - * Specify [`wasm-unsafe-eval`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_webassembly_execution) to permit the Blazor WebAssembly Mono runtime to function. + * In a client-side Blazor app: + * Specify [`wasm-unsafe-eval`](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_webassembly_execution) to permit the client-side Blazor Mono runtime to function. * Specify any additional hashes to permit your required *non-framework scripts* to load. - * In a Blazor Server app, specify hashes to permit required scripts to load. + * In a server-side Blazor app, specify hashes to permit required scripts to load. * [style-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/style-src): Indicates valid sources for stylesheets. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. * If the app uses inline styles, specify `unsafe-inline` to allow the use of your inline styles. @@ -60,10 +62,10 @@ Minimally, specify the following directives and sources for Blazor apps. Add add * [object-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/object-src): Indicates valid sources for the ``, ``, and `` tags. Specify `none` to prevent all URL sources. * [script-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/script-src): Indicates valid sources for scripts. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. - * In a Blazor WebAssembly app: - * Specify `unsafe-eval` to permit the Blazor WebAssembly Mono runtime to function. + * In a client-side Blazor app: + * Specify `unsafe-eval` to permit the client-side Blazor Mono runtime to function. * Specify any additional hashes to permit your required *non-framework scripts* to load. - * In a Blazor Server app, specify hashes to permit required scripts to load. + * In a server-side Blazor app, specify hashes to permit required scripts to load. * [style-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/style-src): Indicates valid sources for stylesheets. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. * If the app uses inline styles, specify `unsafe-inline` to allow the use of your inline styles. @@ -82,10 +84,10 @@ Minimally, specify the following directives and sources for Blazor apps. Add add * [script-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/script-src): Indicates valid sources for scripts. * Specify the `https://stackpath.bootstrapcdn.com/` host source for Bootstrap scripts. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. - * In a Blazor WebAssembly app: - * Specify `unsafe-eval` to permit the Blazor WebAssembly Mono runtime to function. + * In a client-side Blazor app: + * Specify `unsafe-eval` to permit the client-side Blazor Mono runtime to function. * Specify any additional hashes to permit your required *non-framework scripts* to load. - * In a Blazor Server app, specify hashes to permit required scripts to load. + * In a server-side Blazor app, specify hashes to permit required scripts to load. * [style-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/style-src): Indicates valid sources for stylesheets. * Specify the `https://stackpath.bootstrapcdn.com/` host source for Bootstrap stylesheets. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. @@ -105,14 +107,14 @@ Minimally, specify the following directives and sources for Blazor apps. Add add * [script-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/script-src): Indicates valid sources for scripts. * Specify the `https://stackpath.bootstrapcdn.com/` host source for Bootstrap scripts. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. - * In a Blazor WebAssembly app: + * In a client-side Blazor app: * Specify hashes to permit required scripts to load. * Specify `unsafe-eval` to use `eval()` and methods for creating code from strings. - * In a Blazor Server app, specify hashes to permit required scripts to load. + * In a server-side Blazor app, specify hashes to permit required scripts to load. * [style-src](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/style-src): Indicates valid sources for stylesheets. * Specify the `https://stackpath.bootstrapcdn.com/` host source for Bootstrap stylesheets. * Specify `self` to indicate that the app's origin, including the scheme and port number, is a valid source. - * Specify `unsafe-inline` to allow the use of inline styles. The inline declaration is required for the UI in Blazor Server apps for reconnecting the client and server after the initial request. In a future release, inline styling might be removed so that `unsafe-inline` is no longer required. + * Specify `unsafe-inline` to allow the use of inline styles. The inline declaration is required for the UI for reconnecting the client and server after the initial request. In a future release, inline styling might be removed so that `unsafe-inline` is no longer required. * [upgrade-insecure-requests](https://developer.mozilla.org/docs/Web/HTTP/Headers/Content-Security-Policy/upgrade-insecure-requests): Indicates that content URLs from insecure (HTTP) sources should be acquired securely over HTTPS. :::moniker-end @@ -133,13 +135,13 @@ Use a `` tag to apply the policy: * Set the value of the `http-equiv` attribute to `Content-Security-Policy`. * Place the directives in the `content` attribute value. Separate directives with a semicolon (`;`). -* Always place the `meta` tag in the `` content. +* Always place the `meta` tag in the [`` content](xref:blazor/project-structure#location-of-head-and-body-content). -The following sections show example policies for Blazor WebAssembly and Blazor Server. These examples are versioned with this article for each release of Blazor. To use a version appropriate for your release, select the document version with the **Version** dropdown selector on this webpage. +The following sections show example policies. These examples are versioned with this article for each release of Blazor. To use a version appropriate for your release, select the document version with the **Version** dropdown selector on this webpage. -### Blazor WebAssembly +### Client-side Blazor apps -In the `` content of the `wwwroot/index.html` host page, apply the directives described in the *Policy directives* section: +In the [`` content](xref:blazor/project-structure#location-of-head-and-body-content), apply the directives described in the *Policy directives* section: :::moniker range=">= aspnetcore-8.0" @@ -189,7 +191,7 @@ In the `` content of the `wwwroot/index.html` host page, apply the directi ``` > [!NOTE] -> The `sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=` hash represents the [inline](https://github.com/dotnet/aspnetcore/blob/57501251222b199597b9ac16888f362a69eb13c1/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts#L212) script that's used for Blazor WebAssembly. This may be removed in the future. +> The `sha256-v8v3RKRPmN4odZ1CWM5gw80QKPCCWMcpNeOmimNL2AA=` hash represents the [inline](https://github.com/dotnet/aspnetcore/blob/57501251222b199597b9ac16888f362a69eb13c1/src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts#L212) script that's used for client-side Blazor apps. This may be removed in the future. :::moniker-end @@ -241,9 +243,9 @@ Add additional `script-src` and `style-src` hashes as required by the app. Durin The particular script associated with the error is displayed in the console next to the error. -### Blazor Server +### Server-side Blazor apps -In the `` markup ([location of `` content](xref:blazor/project-structure#location-of-head-and-body-content)), apply the directives described in the *Policy directives* section: +In the [`` content](xref:blazor/project-structure#location-of-head-and-body-content), apply the directives described in the *Policy directives* section: :::moniker range=">= aspnetcore-6.0" diff --git a/aspnetcore/blazor/security/includes/httpcontext.md b/aspnetcore/blazor/security/includes/httpcontext.md index 78c1ea81d973..3d07476e859f 100644 --- a/aspnetcore/blazor/security/includes/httpcontext.md +++ b/aspnetcore/blazor/security/includes/httpcontext.md @@ -1,5 +1,5 @@ -**Don't use / directly or indirectly in the Razor components of Blazor Server apps.** Blazor apps run outside of the ASP.NET Core pipeline context. The isn't guaranteed to be available within the , and isn't guaranteed to hold the context that started the Blazor app. +**Don't use / directly or indirectly in the Razor components of server-side Blazor apps.** Blazor apps run outside of the ASP.NET Core pipeline context. The isn't guaranteed to be available within the , and isn't guaranteed to hold the context that started the Blazor app. -The recommended approach for passing request state to the Blazor app is through root component parameters during the app's initial rendering. Alternatively, the app can copy the data into a scoped service in the root component's initialization lifecycle event for use across the app. For more information, see . +The recommended approach for passing request state to the Blazor app is through root component parameters during the app's initial rendering. Alternatively, the app can copy the data into a scoped service in the root component's initialization lifecycle event for use across the app. For more information, see . -A critical aspect of Blazor Server security is that the user attached to a given circuit might become updated at some point after the Blazor circuit is established but the ***isn't updated***. For more information on addressing this situation with custom services, see . +A critical aspect of server-side Blazor security is that the user attached to a given circuit might become updated at some point after the Blazor circuit is established but the ***isn't updated***. For more information on addressing this situation with custom services, see . diff --git a/aspnetcore/blazor/security/includes/shared-state.md b/aspnetcore/blazor/security/includes/shared-state.md index 0ad3dbe24f88..51925347010b 100644 --- a/aspnetcore/blazor/security/includes/shared-state.md +++ b/aspnetcore/blazor/security/includes/shared-state.md @@ -1,4 +1,4 @@ -Blazor server apps live in server memory, and multiple app sessions are hosted within the same process. For each app session, Blazor starts a circuit with its own dependency injection container scope, thus scoped services are unique per Blazor session. +Server-side Blazor apps live in server memory, and multiple app sessions are hosted within the same process. For each app session, Blazor starts a circuit with its own dependency injection container scope, thus scoped services are unique per Blazor session. > [!WARNING] > We don't recommend apps on the same server share state using singleton services unless extreme care is taken, as this can introduce security vulnerabilities, such as leaking user state across circuits. diff --git a/aspnetcore/blazor/security/index.md b/aspnetcore/blazor/security/index.md index 919cd82b100f..4e60fd841e77 100644 --- a/aspnetcore/blazor/security/index.md +++ b/aspnetcore/blazor/security/index.md @@ -14,18 +14,20 @@ uid: blazor/security/index This article describes ASP.NET Core's support for the configuration and management of security in Blazor apps. -Security scenarios differ between Blazor Server and Blazor WebAssembly apps. Because Blazor Server apps run on the server, authorization checks are able to determine: +[!INCLUDE[](~/blazor/includes/location-client-and-server-net31-or-later.md)] + +Security scenarios differ between server-side and client-side Blazor apps. Because a server-side app runs on the server, authorization checks are able to determine: * The UI options presented to a user (for example, which menu entries are available to a user). * Access rules for areas of the app and components. -Blazor WebAssembly apps run on the client. Authorization is *only* used to determine which UI options to show. Since client-side checks can be modified or bypassed by a user, a Blazor WebAssembly app can't enforce authorization access rules. +For a client-side app, authorization is *only* used to determine which UI options to show. Since client-side checks can be modified or bypassed by a user, a client-side app can't enforce authorization access rules. [Razor Pages authorization conventions](xref:security/authorization/razor-pages-authorization) don't apply to routable Razor components. If a non-routable Razor component is [embedded in a page of a Razor Pages app](xref:blazor/components/prerendering-and-integration), the page's authorization conventions indirectly affect the Razor component along with the rest of the page's content. ASP.NET Core Identity is designed to work in the context of HTTP request and response communication, which generally isn't the Blazor app client-server communication model. ASP.NET Core apps that use ASP.NET Core Identity for user management should use Razor Pages instead of Razor components for Identity-related UI, such as user registration, login, logout, and other user management tasks. Building Razor components that directly handle Identity tasks is possible for several scenarios but isn't recommended or supported by Microsoft. -ASP.NET Core abstractions, such as and , aren't supported in Razor components. For more information on using ASP.NET Core Identity with Blazor, see [Scaffold ASP.NET Core Identity into a Blazor Server app](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-blazor-server-project). +ASP.NET Core abstractions, such as and , aren't supported in Razor components. For more information on using ASP.NET Core Identity with Blazor, see [Scaffold ASP.NET Core Identity into a server-side Blazor app](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-server-side-blazor-app). > [!NOTE] > The code examples in this article adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core 6.0 or later. When targeting ASP.NET Core 5.0 or earlier, remove the null type designation (`?`) from examples in this article. @@ -46,13 +48,13 @@ Blazor stores request tokens in component state, which guarantees that antiforge ## Authentication -Blazor uses the existing ASP.NET Core authentication mechanisms to establish the user's identity. The exact mechanism depends on how the Blazor app is hosted, Blazor Server or Blazor WebAssembly. +Blazor uses the existing ASP.NET Core authentication mechanisms to establish the user's identity. The exact mechanism depends on how the Blazor app is hosted, server-side or client-side. -### Blazor Server authentication +### Server-side Blazor authentication -Blazor Server operates over a SignalR connection with the client. [Authentication in SignalR-based apps](xref:signalr/authn-and-authz) is handled when the connection is established. Authentication can be based on a cookie or some other bearer token, but authentication is managed via the SignalR hub and entirely within the [circuit](xref:blazor/hosting-models#blazor-server). +Server-side Blazor operates over a SignalR connection with the client. [Authentication in SignalR-based apps](xref:signalr/authn-and-authz) is handled when the connection is established. Authentication can be based on a cookie or some other bearer token, but authentication is managed via the SignalR hub and entirely within the [circuit](xref:blazor/hosting-models#blazor-server). -The built-in service for Blazor Server apps obtains authentication state data from ASP.NET Core's `HttpContext.User`. This is how authentication state integrates with existing ASP.NET Core authentication mechanisms. +The built-in service obtains authentication state data from ASP.NET Core's `HttpContext.User`. This is how authentication state integrates with existing ASP.NET Core authentication mechanisms. #### Avoid `IHttpContextAccessor`/`HttpContext` in Razor components @@ -62,9 +64,9 @@ The built-in service can provide the current user's data, as shown in the following example. -`Pages/ClaimsPrincipalData.razor`: +`ClaimsPrincipalData.razor`: + +:::moniker range=">= aspnetcore-8.0" ```razor @page "/claims-principle-data" +@attribute [RenderModeServer] @using System.Security.Claims @inject AuthenticationStateProvider AuthenticationStateProvider @@ -139,15 +144,105 @@ The ClaimsPrincipal Data + + + +

@authMessage

+ +@if (claims.Count() > 0) +{ +
    + @foreach (var claim in claims) + { +
  • @claim.Type: @claim.Value
  • + } +
+} + +

@surname

+ +@code { + private string? authMessage; + private string? surname; + private IEnumerable claims = Enumerable.Empty(); + + private async Task GetClaimsPrincipalData() + { + var authState = await AuthenticationStateProvider + .GetAuthenticationStateAsync(); + var user = authState.User; + + if (user.Identity is not null && user.Identity.IsAuthenticated) + { + authMessage = $"{user.Identity.Name} is authenticated."; + claims = user.Claims; + surname = user.FindFirst(c => c.Type == ClaimTypes.Surname)?.Value; + } + else + { + authMessage = "The user is NOT authenticated."; + } + } +} +``` + +:::moniker-end + If `user.Identity.IsAuthenticated` is `true` and because the user is a , claims can be enumerated and membership in roles evaluated. -For more information on dependency injection (DI) and services, see and . For information on how to implement a custom in Blazor Server apps, see . +For more information on dependency injection (DI) and services, see and . For information on how to implement a custom in server-side Blazor apps, see . ## Expose the authentication state as a cascading parameter If authentication state data is required for procedural logic, such as when performing an action triggered by the user, obtain the authentication state data by defining a [cascading parameter](xref:blazor/components/cascading-values-and-parameters) of type `Task<``>`, as the following example demonstrates. -`Pages/CascadeAuthState.razor`: +`CascadeAuthState.razor`: + +:::moniker range=">= aspnetcore-8.0" + +```razor +@page "/cascade-auth-state" +@attribute [RenderModeServer] + +

Cascade Auth State

+ +

@authMessage

+ +@code { + private string authMessage = "The user is NOT authenticated."; + + [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?.Identity is not null && user.Identity.IsAuthenticated) + { + authMessage = $"{user.Identity.Name} is authenticated."; + } + } + } +} +``` + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" ```razor @page "/cascade-auth-state" @@ -178,14 +273,16 @@ If authentication state data is required for procedural logic, such as when perf } ``` +:::moniker-end + If `user.Identity.IsAuthenticated` is `true`, claims can be enumerated and membership in roles evaluated. -Set up the `Task<``>` [cascading parameter](xref:blazor/components/cascading-values-and-parameters) using the and components in the `App` component. +Set up the `Task<``>` [cascading parameter](xref:blazor/components/cascading-values-and-parameters) using the and components. -> [!NOTE] -> When you create a Blazor app from one of the Blazor project templates with authentication enabled, the `App` component includes the and components shown in the following example. A Blazor WebAssembly app includes the required service registrations as well. Additional information is presented in the [Customize unauthorized content with the Router component](#customize-unauthorized-content-with-the-router-component) section. + -`App.razor`: +When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the and components shown in the following example. A client-side Blazor app includes the required service registrations as well. Additional information is presented in the [Customize unauthorized content with the Router component](#customize-unauthorized-content-with-the-router-component) section. ```razor @@ -195,9 +292,7 @@ Set up the `Task<` ... - - ... - + ... ``` @@ -208,14 +303,14 @@ Set up the `Task<`` element in the preceding example, can only be invoked by an authorized user. > [!WARNING] -> Client-side markup and methods associated with an are only protected from view and execution in the ***rendered UI*** in Blazor WebAssembly apps. In order to protect authorized content and secure methods for Blazor WebAssembly apps, the content is usually supplied by a secure, authorized web API call to a server API and never stored in the app. For more information, see and . +> Client-side markup and methods associated with an are only protected from view and execution in the ***rendered UI*** in client-side Blazor apps. In order to protect authorized content and secure methods in client-side Blazor, the content is usually supplied by a secure, authorized web API call to a server API and never stored in the app. For more information, see and . The content of `` and `` tags can include arbitrary items, such as other interactive components. @@ -341,7 +436,7 @@ Pascal case is typically used for role and policy names (for example, `BillingAd ### Content displayed during asynchronous authentication -Blazor allows for authentication state to be determined *asynchronously*. The primary scenario for this approach is in Blazor WebAssembly apps that make a request to an external endpoint for authentication. +Blazor allows for authentication state to be determined *asynchronously*. The primary scenario for this approach is in client-side Blazor apps that make a request to an external endpoint for authentication. While authentication is in progress, displays no content by default. To display content while authentication occurs, use the `` tag: @@ -356,7 +451,7 @@ While authentication is in progress, ``` -This approach isn't normally applicable to Blazor Server apps. Blazor Server apps know the authentication state as soon as the state is established. content can be provided in a Blazor Server app's component, but the content is never displayed. +This approach isn't normally applicable to server-side Blazor apps. Server-side Blazor apps know the authentication state as soon as the state is established. content can be provided in an app's component, but the content is never displayed. ## `[Authorize]` attribute @@ -407,7 +502,10 @@ Not authorized. To authorize users for resources, pass the request's route data to the parameter of . -In the content for a requested route in the `App` component (`App.razor`): + + +In the content for a requested route: ```razor and : @@ -460,7 +558,31 @@ The preceding example is an oversimplified authorization policy, merely used to In the following `EditUser` component, the resource at `/users/{id}/edit` has a route parameter for the user's identifier (`{id}`). The component uses the preceding `EditUser` authorization policy to determine if the route value for `id` starts with `EMP`. If `id` starts with `EMP`, the policy succeeds and access to the component is authorized. If `id` starts with a value other than `EMP` or if `id` is an empty string, the policy fails, and the component doesn't load. -`Pages/EditUser.razor`: +`EditUser.razor`: + +:::moniker-end + +:::moniker range=">= aspnetcore-8.0" + +```razor +@page "/users/{id}/edit" +@attribute [RenderModeServer] +@using Microsoft.AspNetCore.Authorization +@attribute [Authorize(Policy = "EditUser")] + +

Edit User

+ +

The "EditUser" policy is satisfied! Id starts with 'EMP'.

+ +@code { + [Parameter] + public string? Id { get; set; } +} +``` + +:::moniker-end + +:::moniker range=">= aspnetcore-5.0 < aspnetcore-8.0" ```razor @page "/users/{id}/edit" @@ -483,11 +605,11 @@ In the following `EditUser` component, the resource at `/users/{id}/edit` has a The component, in conjunction with the component, allows the app to specify custom content if: + + * The user fails an [`[Authorize]`](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) condition applied to the component. The markup of the [``](xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView.NotAuthorized?displayProperty=nameWithType) element is displayed. The [`[Authorize]`](xref:Microsoft.AspNetCore.Authorization.AuthorizeAttribute) attribute is covered in the [`[Authorize]` attribute](#authorize-attribute) section. * Asynchronous authorization is in progress, which usually means that the process of authenticating the user is in progress. The markup of the [``](xref:Microsoft.AspNetCore.Components.Authorization.AuthorizeRouteView.Authorizing?displayProperty=nameWithType) element is displayed. -* Content isn't found. The markup of the [``](xref:Microsoft.AspNetCore.Components.Routing.Router.NotFound?displayProperty=nameWithType) element is displayed. - -In the `App` component (`App.razor`): ```razor @@ -502,11 +624,7 @@ In the `App` component (`App.razor`):
- - - ... - - + ... ``` @@ -519,9 +637,9 @@ If the `` tag isn't specified, the ` content of the Blazor Router in the template's [`App` component (reference source)](https://github.com/dotnet/aspnetcore/blob/release/7.0/src/ProjectTemplates/Web.ProjectTemplates/content/ComponentsWebAssembly-CSharp/Client/App.razor). When a user isn't authenticated (`context.User.Identity?.IsAuthenticated != true`), the `RedirectToLogin` component redirects the browser to the `authentication/login` endpoint for authentication. The user is returned to the requested URL after authenticating with the identity provider. + -[!INCLUDE[](~/includes/aspnetcore-repo-ref-source-links.md)] +An app created from the client-side Blazor project template with authentication enabled includes a `RedirectToLogin` component, which is positioned in the `` content of the Blazor's router. When a user isn't authenticated (`context.User.Identity?.IsAuthenticated != true`), the `RedirectToLogin` component redirects the browser to the `authentication/login` endpoint for authentication. The user is returned to the requested URL after authenticating with the identity provider. ## Procedural logic @@ -533,17 +651,20 @@ In the following example: * The `user.IsInRole("admin")` executes code for users in the 'Admin' role. * The `(await AuthorizationService.AuthorizeAsync(user, "content-editor")).Succeeded` executes code for users satisfying the 'content-editor' policy. -A Blazor Server app includes the appropriate namespaces by default when created from the Blazor Server project template. In a Blazor WebAssembly app, confirm the presence of the and namespaces either in the component or in the app's `_Imports.razor` file: +A server-side Blazor app includes the appropriate namespaces by default when created from the project template. In a client-side Blazor app, confirm the presence of the and namespaces either in the component or in the app's `_Imports.razor` file: ```razor @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Components.Authorization ``` -`Pages/ProceduralLogic.razor`: +`ProceduralLogic.razor`: + +:::moniker range=">= aspnetcore-8.0" ```razor @page "/procedural-logic" +@attribute [RenderModeServer] @inject IAuthorizationService AuthorizationService

Procedural Logic Example

@@ -584,6 +705,54 @@ A Blazor Server app includes the appropriate namespaces by default when created } ``` +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +```razor +@page "/procedural-logic" +@inject IAuthorizationService AuthorizationService + +

Procedural Logic Example

+ + + +@code { + [CascadingParameter] + private Task? authenticationState { get; set; } + + private async Task DoSomething() + { + if (authenticationState is not null) + { + var authState = await authenticationState; + var user = authState?.User; + + if (user is not null) + { + if (user.Identity is not null && user.Identity.IsAuthenticated) + { + // ... + } + + if (user.IsInRole("Admin")) + { + // ... + } + + if ((await AuthorizationService.AuthorizeAsync(user, "content-editor")) + .Succeeded) + { + // ... + } + } + } + } +} +``` + +:::moniker-end + ## Troubleshoot errors Common errors: @@ -592,7 +761,7 @@ Common errors: * **`null` value is received for `authenticationStateTask`** -It's likely that the project wasn't created using a Blazor Server template with authentication enabled. Wrap a `` around some part of the UI tree, for example in the `App` component (`App.razor`) as follows: +It's likely that the project wasn't created using a server-side Blazor template with authentication enabled. Wrap a `` around some part of the UI tree, for example around the Blazor router: ```razor diff --git a/aspnetcore/blazor/security/server/additional-scenarios.md b/aspnetcore/blazor/security/server/additional-scenarios.md index eb2680527b94..68d50ad68747 100644 --- a/aspnetcore/blazor/security/server/additional-scenarios.md +++ b/aspnetcore/blazor/security/server/additional-scenarios.md @@ -1,36 +1,39 @@ --- -title: ASP.NET Core Blazor Server additional security scenarios +title: Server-side ASP.NET Core Blazor additional security scenarios author: guardrex -description: Learn how to configure Blazor Server for additional security scenarios. +description: Learn how to configure server-side Blazor for additional security scenarios. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc ms.date: 02/16/2023 uid: blazor/security/server/additional-scenarios --- -# ASP.NET Core Blazor Server additional security scenarios +# Server-side ASP.NET Core Blazor additional security scenarios [!INCLUDE[](~/includes/not-latest-version.md)] -This article explains how to configure Blazor Server for additional security scenarios, including how to pass tokens to a Blazor Server app. +This article explains how to configure server-side Blazor for additional security scenarios, including how to pass tokens to a Blazor app. -## Pass tokens to a Blazor Server app +[!INCLUDE[](~/blazor/includes/location-client-and-server-net31-or-later.md)] + +## Pass tokens to a server-side Blazor app + passing antiforgery tokens for BWAs (and this guidance is + probably changing for the other tokens as well), so we'll + probably version the section out in favor of a new section + on BWA token handling. --> -Tokens available outside of the Razor components in a Blazor Server app can be passed to components with the approach described in this section. The example in this section focuses on passing access, refresh, and [anti-request forgery (XSRF) token](xref:security/anti-request-forgery) tokens to the Blazor app, but the approach is valid for other HTTP context state. +Tokens available outside of the Razor components in a server-side Blazor app can be passed to components with the approach described in this section. The example in this section focuses on passing access, refresh, and [anti-request forgery (XSRF) token](xref:security/anti-request-forgery) tokens to the Blazor app, but the approach is valid for other HTTP context state. > [!NOTE] > Passing the XSRF token to Razor components is useful in scenarios where components POST to Identity or other endpoints that require validation. If your app only requires access and refresh tokens, you can remove the XSRF token code from the following example. -Authenticate the Blazor Server app as you would with a regular Razor Pages or MVC app. Provision and save the tokens to the authentication cookie. +Authenticate the app as you would with a regular Razor Pages or MVC app. Provision and save the tokens to the authentication cookie. :::moniker range=">= aspnetcore-6.0" -In `Program.cs`: +In the `Program` file: ```csharp using Microsoft.AspNetCore.Authentication.OpenIdConnect; @@ -127,7 +130,7 @@ public class TokenProvider :::moniker range=">= aspnetcore-6.0" -In `Program.cs`, add services for: +In the `Program` file, add services for: * : Used in a `WeatherForecastService` class that obtains weather data from a server API with an access token. * `TokenProvider`: Holds the access and refresh tokens. @@ -351,10 +354,14 @@ public class WeatherForecastService :::moniker-end -For an XSRF token passed to a component, inject the `TokenProvider` and add the XSRF token to the POST request. The following example adds the token to a logout endpoint POST. The scenario for the following example is that the logout endpoint (`Areas/Identity/Pages/Account/Logout.cshtml`, [scaffolded into the app](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-blazor-server-project)) doesn't specify an (`@attribute [IgnoreAntiforgeryToken]`) because it performs some action in addition to a normal logout operation that must be protected. The endpoint requires a valid XSRF token to successfully process the request. +For an XSRF token passed to a component, inject the `TokenProvider` and add the XSRF token to the POST request. The following example adds the token to a logout endpoint POST. The scenario for the following example is that the logout endpoint (`Areas/Identity/Pages/Account/Logout.cshtml`, [scaffolded into the app](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-server-side-blazor-app)) doesn't specify an (`@attribute [IgnoreAntiforgeryToken]`) because it performs some action in addition to a normal logout operation that must be protected. The endpoint requires a valid XSRF token to successfully process the request. In a component that presents a **Logout** button to authorized users: + + ```razor @inject TokenProvider TokenProvider @@ -378,7 +385,7 @@ In a component that presents a **Logout** button to authorized users: :::moniker range=">= aspnetcore-6.0" -For an app that uses more than one Authentication Middleware and thus has more than one authentication scheme, the scheme that Blazor uses can be explicitly set in the endpoint configuration of `Program.cs`. The following example sets the OpenID Connect (OIDC) scheme: +For an app that uses more than one Authentication Middleware and thus has more than one authentication scheme, the scheme that Blazor uses can be explicitly set in the endpoint configuration of the `Program` file. The following example sets the OpenID Connect (OIDC) scheme: :::moniker-end @@ -659,7 +666,7 @@ internal sealed class UserCircuitHandler : CircuitHandler, IDisposable :::moniker range=">= aspnetcore-6.0" -In `Program.cs`: +In the `Program` file: ```csharp using Microsoft.AspNetCore.Components.Server.Circuits; @@ -723,7 +730,7 @@ public class UserServiceMiddleware :::moniker range=">= aspnetcore-6.0" -Immediately before the call to `app.MapBlazorHub()` in `Program.cs`, call the middleware: +Immediately before the call to `app.MapBlazorHub()` in the `Program` file, call the middleware: :::moniker-end @@ -788,7 +795,7 @@ public class AuthenticationStateHandler : DelegatingHandler } ``` -In `Program.cs`, register the `AuthenticationStateHandler` and add the handler to the that creates instances: +In the `Program` file, register the `AuthenticationStateHandler` and add the handler to the that creates instances: ```csharp builder.Services.AddTransient(); diff --git a/aspnetcore/blazor/security/server/index.md b/aspnetcore/blazor/security/server/index.md index 919485b95690..2683a16082b9 100644 --- a/aspnetcore/blazor/security/server/index.md +++ b/aspnetcore/blazor/security/server/index.md @@ -1,42 +1,44 @@ --- -title: Secure ASP.NET Core Blazor Server apps +title: Secure ASP.NET Core server-side Blazor apps author: guardrex -description: Learn how to secure Blazor Server apps as ASP.NET Core applications. +description: Learn how to secure server-side Blazor apps as ASP.NET Core applications. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc ms.date: 02/16/2023 uid: blazor/security/server/index --- -# Secure ASP.NET Core Blazor Server apps +# Secure ASP.NET Core server-side Blazor apps [!INCLUDE[](~/includes/not-latest-version.md)] -This article explains how to secure Blazor Server apps as ASP.NET Core applications. +This article explains how to secure server-side Blazor apps as ASP.NET Core applications. -Blazor Server apps are configured for security in the same manner as ASP.NET Core apps. For more information, see the articles under . Topics under this overview apply specifically to Blazor Server. +[!INCLUDE[](~/blazor/includes/location-client-and-server-net31-or-later.md)] -In Blazor Server apps, the authentication context is only established when the app starts, which is when the app first connects to the WebSocket. The authentication context is maintained for the lifetime of the circuit. Blazor Server apps periodically revalidate the user's authentication state, currently every 30 minutes by default. +Server-side Blazor apps are configured for security in the same manner as ASP.NET Core apps. For more information, see the articles under . + +The authentication context is only established when the app starts, which is when the app first connects to the WebSocket. The authentication context is maintained for the lifetime of the circuit. Apps periodically revalidate the user's authentication state, currently every 30 minutes by default. If the app must capture users for custom services or react to updates to the user, see . -Blazor Server differs from a traditional server-rendered web apps that make new HTTP requests with cookies on every page navigation. Authentication is checked during navigation events. However, cookies aren't involved. Cookies are only sent when making an HTTP request to a server, which isn't what happens when the user navigates in a Blazor Server app. During navigation, the user's authentication state is checked within the Blazor circuit, which you can update at any time on the server using the [`RevalidatingAuthenticationStateProvider` abstraction](#additional-security-abstractions). +Blazor differs from a traditional server-rendered web apps that make new HTTP requests with cookies on every page navigation. Authentication is checked during navigation events. However, cookies aren't involved. Cookies are only sent when making an HTTP request to a server, which isn't what happens when the user navigates in a Blazor app. During navigation, the user's authentication state is checked within the Blazor circuit, which you can update at any time on the server using the [`RevalidatingAuthenticationStateProvider` abstraction](#additional-security-abstractions). > [!IMPORTANT] -> Implementing a custom `NavigationManager` to achieve authentication validation during navigation isn't recommended for Blazor Server apps. If the app must execute custom authentication state logic during navigation, use a [custom `AuthenticationStateProvider`](#implement-a-custom-authenticationstateprovider). +> Implementing a custom `NavigationManager` to achieve authentication validation during navigation isn't recommended. If the app must execute custom authentication state logic during navigation, use a [custom `AuthenticationStateProvider`](#implement-a-custom-authenticationstateprovider). > [!NOTE] > The code examples in this article adopt [nullable reference types (NRTs) and .NET compiler null-state static analysis](xref:migration/50-to-60#nullable-reference-types-nrts-and-net-compiler-null-state-static-analysis), which are supported in ASP.NET Core 6.0 or later. When targeting ASP.NET Core 5.0 or earlier, remove the null type designation (`?`) from the examples in this article. -## Blazor Server project template +## Project template -The [Blazor Server project template](xref:blazor/project-structure) can be configured for authentication when the project is created. +Create a new server-side Blazor app by following the guidance in . # [Visual Studio](#tab/visual-studio) -Follow the Visual Studio guidance in to create a new Blazor Server project with an authentication mechanism. +After choosing the server-side app template and configuring the project, select the app's authentication under **Authentication type**: -After choosing the **Blazor Server App** template and configuring the project, select the app's authentication under **Authentication type**: + * **Individual Accounts**: User accounts are stored within the app using ASP.NET Core [Identity](xref:security/authentication/identity). * **Microsoft identity platform**: For more information, see . @@ -44,12 +46,15 @@ After choosing the **Blazor Server App** template and configuring the project, s # [Visual Studio Code](#tab/visual-studio-code) -Follow the Visual Studio Code guidance in to create a new Blazor Server project with an authentication mechanism: +When issuing the .NET CLI command to create and configure the server-side Blazor app, indicate the authentication mechanism with the `-au|--auth` option: ```dotnetcli -dotnet new blazorserver -o {PROJECT NAME} -au {AUTHENTICATION} +-au {AUTHENTICATION} ``` +> [!NOTE] +> For the full command, see . + Permissible authentication values for the `{AUTHENTICATION}` placeholder are shown in the following table. | Authentication mechanism | Description | @@ -61,29 +66,27 @@ Permissible authentication values for the `{AUTHENTICATION}` placeholder are sho | `MultiOrg` | Organizational authentication for multiple tenants | | `Windows` | Windows Authentication | -Using the `-o|--output` option, the command uses the value provided for the `{PROJECT NAME}` placeholder to: - -* Create a folder for the project. -* Name the project. - For more information, see the [`dotnet new`](/dotnet/core/tools/dotnet-new) command in the .NET Core Guide. # [Visual Studio for Mac](#tab/visual-studio-mac) -1. Follow the Visual Studio for Mac guidance in to create a Blazor Server app. + -1. Select **Individual Authentication (in-app)** from the **Authentication** dropdown list. +After choosing the server-side app template and configuring the project, select **Individual Authentication (in-app)** from the **Authentication** dropdown list. -1. The app is created for individual users stored in the app with ASP.NET Core Identity. +The app is created for individual users stored in the app with ASP.NET Core Identity. # [.NET Core CLI](#tab/netcore-cli/) -Create a new Blazor Server project with an authentication mechanism using the following command in a command shell: +When issuing the .NET CLI command to create and configure the server-side Blazor app, indicate the authentication mechanism with the `-au|--auth` option: ```dotnetcli -dotnet new blazorserver -o {PROJECT NAME} -au {AUTHENTICATION} +-au {AUTHENTICATION} ``` +> [!NOTE] +> For the full command, see . + Permissible authentication values for the `{AUTHENTICATION}` placeholder are shown in the following table. | Authentication mechanism | Description | @@ -95,36 +98,33 @@ Permissible authentication values for the `{AUTHENTICATION}` placeholder are sho | `MultiOrg` | Organizational authentication for multiple tenants | | `Windows` | Windows Authentication | -Using the `-o|--output` option, the command uses the value provided for the `{PROJECT NAME}` placeholder to: - -* Create a folder for the project. -* Name the project. - For more information: * See the [`dotnet new`](/dotnet/core/tools/dotnet-new) command in the .NET Core Guide. -* Execute the help command for the Blazor Server template (`blazorserver`) in a command shell: +* Execute the help command for the template in a command shell: ```dotnetcli - dotnet new blazorserver --help + dotnet new {PROJECT TEMPLATE} --help ``` + In the preceding command, the `{PROJECT TEMPLATE}` placeholder is the project template. + --- ## Scaffold Identity :::moniker range=">= aspnetcore-6.0" -For more information on scaffolding Identity into a Blazor Server project, see . +For more information on scaffolding Identity into a server-side Blazor app, see . :::moniker-end :::moniker range="< aspnetcore-6.0" -Scaffold Identity into a Blazor Server project: +Scaffold Identity into a server-side Blazor app: -* [Without existing authorization](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-blazor-server-project-without-existing-authorization). -* [With authorization](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-blazor-server-project-with-authorization). +* [Without existing authorization](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-server-side-blazor-app-without-existing-authorization). +* [With authorization](xref:security/authentication/scaffold-identity#scaffold-identity-into-a-server-side-blazor-app-with-authorization). :::moniker-end @@ -167,7 +167,7 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider :::moniker range=">= aspnetcore-6.0" -The `CustomAuthenticationStateProvider` service is registered in `Program.cs` ***after*** the call to : +The `CustomAuthenticationStateProvider` service is registered in the `Program` file ***after*** the call to : ```csharp using Microsoft.AspNetCore.Components.Authorization; @@ -203,9 +203,10 @@ services.AddScoped and to the `App` component. +Confirm or add an and for the Blazor router: -In `App.razor`: + ```razor @@ -215,15 +216,13 @@ In `App.razor`: DefaultLayout="@typeof(MainLayout)" /> ... - - ... - + ... ``` > [!NOTE] -> When you create a Blazor app from one of the Blazor project templates with authentication enabled, the `App` component includes the and components shown in the preceding example. For more information, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. +> When you create a Blazor app from one of the Blazor project templates with authentication enabled, the app includes the and components shown in the preceding example. For more information, see with additional information presented in the article's [Customize unauthorized content with the Router component](xref:blazor/security/index#customize-unauthorized-content-with-the-router-component) section. An demonstrates the authenticated user's name in any component: @@ -286,6 +285,39 @@ In a component: * Add a field to hold the user's identifier. * Add a button and a method to cast the to `CustomAuthenticationStateProvider` and call `AuthenticateUser` with the user's identifier. +:::moniker range=">= aspnetcore-8.0" + +```razor +@attribute [RenderModeServer] +@inject AuthenticationStateProvider AuthenticationStateProvider + + + + + + +

Hello, @context.User.Identity?.Name!

+
+ +

You're not authorized.

+
+
+ +@code { + public string userIdentifier = string.Empty; + + private void SignIn() + { + ((CustomAuthenticationStateProvider)AuthenticationStateProvider) + .AuthenticateUser(userIdentifier); + } +} +``` + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + ```razor @inject AuthenticationStateProvider AuthenticationStateProvider @@ -312,6 +344,8 @@ In a component: } ``` +:::moniker-end + The preceding approach can be enhanced to trigger notifications of authentication state changes via a custom service. The following `AuthenticationService` maintains the current user's claims principal in a backing field (`currentUser`) with an event (`UserChanged`) that the can subscribe to, where the event invokes . With the additional configuration later in this section, the `AuthenticationService` can be injected into a component with logic that sets the `CurrentUser` to trigger the `UserChanged` event. ```csharp @@ -340,7 +374,7 @@ public class AuthenticationService :::moniker range=">= aspnetcore-6.0" -In `Program.cs`, register the `AuthenticationService` in the dependency injection container: +In the `Program` file, register the `AuthenticationService` in the dependency injection container: ```csharp builder.Services.AddScoped(); @@ -387,7 +421,10 @@ public class CustomAuthenticationStateProvider : AuthenticationStateProvider The following component's `SignIn` method creates a claims principal for the user's identifier to set on `AuthenticationService.CurrentUser`: +:::moniker range=">= aspnetcore-8.0" + ```razor +@attribute [RenderModeServer] @inject AuthenticationService AuthenticationService @@ -423,6 +460,48 @@ The following component's `SignIn` method creates a claims principal for the use } ``` +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +```razor +@inject AuthenticationService AuthenticationService + + + + + + +

Hello, @context.User.Identity?.Name!

+
+ +

You're not authorized.

+
+
+ +@code { + public string userIdentifier = string.Empty; + + private void SignIn() + { + var currentUser = AuthenticationService.CurrentUser; + + var identity = new ClaimsIdentity( + new[] + { + new Claim(ClaimTypes.Name, userIdentifier), + }, + "Custom Authentication"); + + var newUser = new ClaimsPrincipal(identity); + + AuthenticationService.CurrentUser = newUser; + } +} +``` + +:::moniker-end + ## Inject `AuthenticationStateProvider` for services scoped to a component Don't attempt to resolve within a custom scope because it results in the creation of a new instance of the that isn't correctly initialized. @@ -451,11 +530,11 @@ public class ExampleService } ``` -Register the service as scoped. In a Blazor Server app, scoped services have a lifetime equal to the duration of the client connection [circuit](xref:blazor/hosting-models#blazor-server). +Register the service as scoped. In a server-side Blazor app, scoped services have a lifetime equal to the duration of the client connection [circuit](xref:blazor/hosting-models#blazor-server). :::moniker range=">= aspnetcore-6.0" -In `Program.cs`: +In the `Program` file: ```csharp builder.Services.AddScoped(); @@ -479,10 +558,13 @@ In the following `InjectAuthStateProvider` component: * The is injected and passed to `ExampleService.ExampleMethod`. * `ExampleService` is resolved with and , which returns the correct, initialized instance of `ExampleService` that exists for the lifetime of the user's circuit. -`Pages/InjectAuthStateProvider.razor`: +`InjectAuthStateProvider.razor`: + +:::moniker range=">= aspnetcore-8.0" ```razor @page "/inject-auth-state-provider" +@attribute [RenderModeServer] @inject AuthenticationStateProvider AuthenticationStateProvider @inherits OwningComponentBase @@ -503,9 +585,39 @@ In the following `InjectAuthStateProvider` component: } ``` +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + +```razor +@page "/inject-auth-state-provider" +@inject AuthenticationStateProvider AuthenticationStateProvider +@inherits OwningComponentBase + +

Inject AuthenticationStateProvider Example

+ +

@message

+ +@code { + private string? message; + private ExampleService? ExampleService { get; set; } + + protected override async Task OnInitializedAsync() + { + ExampleService = ScopedServices.GetRequiredService(); + + message = await ExampleService.ExampleMethod(AuthenticationStateProvider); + } +} +``` + +:::moniker-end + For more information, see the guidance on in . - + +:::moniker range="< aspnetcore-8.0" ## Unauthorized content display while prerendering with a custom `AuthenticationStateProvider` @@ -523,13 +635,13 @@ To avoid showing unauthorized content while prerendering with a [custom `Authent * Authenticate the user on the server before the app starts: To adopt this approach, the app must respond to a user's initial request with the Identity-based sign-in page or view and prevent any requests to Blazor endpoints until they're authenticated. For more information, see . After authentication, unauthorized content in prerendered Razor components is only shown when the user is truly unauthorized to view the content. ---> +:::moniker-end ## User state management In spite of the word "state" in the name, isn't for storing *general user state*. only indicates the user's authentication state to the app, whether they are signed into the app and who they are signed in as. -In Blazor Server apps, authentication uses the same ASP.NET Core Identity authentication as Razor Pages and MVC apps. The user state stored for ASP.NET Core Identity flows to Blazor without adding additional code to the app. Follow the guidance in the ASP.NET Core Identity articles and tutorials for the Identity features to take effect in the Blazor parts of the app. +Authentication uses the same ASP.NET Core Identity authentication as Razor Pages and MVC apps. The user state stored for ASP.NET Core Identity flows to Blazor without adding additional code to the app. Follow the guidance in the ASP.NET Core Identity articles and tutorials for the Identity features to take effect in the Blazor parts of the app. For guidance on general state management outside of ASP.NET Core Identity, see . @@ -541,7 +653,7 @@ Two additional abstractions participate in managing authentication state: * ([reference source](https://github.com/dotnet/aspnetcore/blob/main/src/Components/Server/src/Circuits/RevalidatingServerAuthenticationStateProvider.cs)): A base class for services used by the Blazor framework to receive an authentication state from the host environment and revalidate it at regular intervals. - For a Blazor Server app created from the Blazor Server project template with authentication enabled, the default 30 minute revalidation interval can be adjusted in [`RevalidatingIdentityAuthenticationStateProvider` (`Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs`)](https://github.com/dotnet/aspnetcore/blob/release/7.0/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs). The following example shortens the interval to 20 minutes: + The default 30 minute revalidation interval can be adjusted in [`RevalidatingIdentityAuthenticationStateProvider` (`Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs`)](https://github.com/dotnet/aspnetcore/blob/release/7.0/src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs). The following example shortens the interval to 20 minutes: ```csharp protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(20); diff --git a/aspnetcore/blazor/security/server/threat-mitigation.md b/aspnetcore/blazor/security/server/threat-mitigation.md index ac0d5cdf343b..da854a9c369f 100644 --- a/aspnetcore/blazor/security/server/threat-mitigation.md +++ b/aspnetcore/blazor/security/server/threat-mitigation.md @@ -1,24 +1,26 @@ --- -title: Threat mitigation guidance for ASP.NET Core Blazor Server +title: Threat mitigation guidance for server-side ASP.NET Core Blazor author: guardrex -description: Learn how to mitigate security threats to Blazor Server apps. +description: Learn how to mitigate security threats to server-side Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.custom: mvc ms.date: 02/21/2023 uid: blazor/security/server/threat-mitigation --- -# Threat mitigation guidance for ASP.NET Core Blazor Server +# Threat mitigation guidance for server-side ASP.NET Core Blazor [!INCLUDE[](~/includes/not-latest-version.md)] -This article explains how to mitigate security threats to Blazor Server apps. +This article explains how to mitigate security threats to server-side Blazor apps. -Blazor Server apps adopt a *stateful* data processing model, where the server and client maintain a long-lived relationship. The persistent state is maintained by a [circuit](xref:blazor/state-management), which can span connections that are also potentially long-lived. +[!INCLUDE[](~/blazor/includes/location-client-and-server-net31-or-later.md)] -When a user visits a Blazor Server site, the server creates a circuit in the server's memory. The circuit indicates to the browser what content to render and responds to events, such as when the user selects a button in the UI. To perform these actions, a circuit invokes JavaScript functions in the user's browser and .NET methods on the server. This two-way JavaScript-based interaction is referred to as [JavaScript interop (JS interop)](xref:blazor/js-interop/call-javascript-from-dotnet). +Apps adopt a *stateful* data processing model, where the server and client maintain a long-lived relationship. The persistent state is maintained by a [circuit](xref:blazor/state-management), which can span connections that are also potentially long-lived. -Because JS interop occurs over the Internet and the client uses a remote browser, Blazor Server apps share most web app security concerns. This topic describes common threats to Blazor Server apps and provides threat mitigation guidance focused on Internet-facing apps. +When a user visits a site, the server creates a circuit in the server's memory. The circuit indicates to the browser what content to render and responds to events, such as when the user selects a button in the UI. To perform these actions, a circuit invokes JavaScript functions in the user's browser and .NET methods on the server. This two-way JavaScript-based interaction is referred to as [JavaScript interop (JS interop)](xref:blazor/js-interop/call-javascript-from-dotnet). + +Because JS interop occurs over the Internet and the client uses a remote browser, apps share most web app security concerns. This topic describes common threats to server-side Blazor apps and provides threat mitigation guidance focused on Internet-facing apps. In constrained environments, such as inside corporate networks or intranets, some of the mitigation guidance either: @@ -49,15 +51,15 @@ Resources external to the Blazor framework, such as databases and file handles ( CPU exhaustion can occur when one or more clients force the server to perform intensive CPU work. -For example, consider a Blazor Server app that calculates a *Fibonnacci number*. A Fibonnacci number is produced from a Fibonnacci sequence, where each number in the sequence is the sum of the two preceding numbers. The amount of work required to reach the answer depends on the length of the sequence and the size of the initial value. If the app doesn't place limits on a client's request, the CPU-intensive calculations may dominate the CPU's time and diminish the performance of other tasks. Excessive resource consumption is a security concern impacting availability. +For example, consider an app that calculates a *Fibonnacci number*. A Fibonnacci number is produced from a Fibonnacci sequence, where each number in the sequence is the sum of the two preceding numbers. The amount of work required to reach the answer depends on the length of the sequence and the size of the initial value. If the app doesn't place limits on a client's request, the CPU-intensive calculations may dominate the CPU's time and diminish the performance of other tasks. Excessive resource consumption is a security concern impacting availability. -CPU exhaustion is a concern for all public-facing apps. In regular web apps, requests and connections time out as a safeguard, but Blazor Server apps don't provide the same safeguards. Blazor Server apps must include appropriate checks and limits before performing potentially CPU-intensive work. +CPU exhaustion is a concern for all public-facing apps. In regular web apps, requests and connections time out as a safeguard, but Blazor apps don't provide the same safeguards. Blazor apps must include appropriate checks and limits before performing potentially CPU-intensive work. ### Memory Memory exhaustion can occur when one or more clients force the server to consume a large amount of memory. -For example, consider a Blazor-server side app with a component that accepts and displays a list of items. If the Blazor app doesn't place limits on the number of items allowed or the number of items rendered back to the client, the memory-intensive processing and rendering may dominate the server's memory to the point where performance of the server suffers. The server may crash or slow to the point that it appears to have crashed. +For example, consider an app with a component that accepts and displays a list of items. If the Blazor app doesn't place limits on the number of items allowed or the number of items rendered back to the client, the memory-intensive processing and rendering may dominate the server's memory to the point where performance of the server suffers. The server may crash or slow to the point that it appears to have crashed. Consider the following scenario for maintaining and displaying a list of items that pertain to a potential memory exhaustion scenario on the server: @@ -74,9 +76,9 @@ Consider the following scenario for maintaining and displaying a list of items t :::moniker-end -Blazor Server apps offer a similar programming model to other UI frameworks for stateful apps, such as WPF, Windows Forms, or Blazor WebAssembly. The main difference is that in several of the UI frameworks the memory consumed by the app belongs to the client and only affects that individual client. For example, a Blazor WebAssembly app runs entirely on the client and only uses client memory resources. In the Blazor Server scenario, the memory consumed by the app belongs to the server and is shared among clients on the server instance. +Blazor apps offer a similar programming model to other UI frameworks for stateful apps, such as WPF, Windows Forms, or Blazor WebAssembly. The main difference is that in several of the UI frameworks the memory consumed by the app belongs to the client and only affects that individual client. For example, a Blazor WebAssembly app runs entirely on the client and only uses client memory resources. For a server-side Blazor app, the memory consumed by the app belongs to the server and is shared among clients on the server instance. -Server-side memory demands are a consideration for all Blazor Server apps. However, most web apps are stateless, and the memory used while processing a request is released when the response is returned. As a general recommendation, don't permit clients to allocate an unbound amount of memory as in any other server-side app that persists client connections. The memory consumed by a Blazor Server app persists for a longer time than a single request. +Server-side memory demands are a consideration for all server-side Blazor apps. However, most web apps are stateless, and the memory used while processing a request is released when the response is returned. As a general recommendation, don't permit clients to allocate an unbound amount of memory as in any other server-side app that persists client connections. The memory consumed by a server-side Blazor app persists for a longer time than a single request. > [!NOTE] > During development, a profiler can be used or a trace captured to assess memory demands of clients. A profiler or trace won't capture the memory allocated to a specific client. To capture the memory use of a specific client during development, capture a dump and examine the memory demand of all the objects rooted at a user's circuit. @@ -85,9 +87,9 @@ Server-side memory demands are a consideration for all Blazor Server apps. Howev Connection exhaustion can occur when one or more clients open too many concurrent connections to the server, preventing other clients from establishing new connections. -Blazor clients establish a single connection per session and keep the connection open for as long as the browser window is open. Given the persistent nature of the connections and the stateful nature of Blazor Server apps, connection exhaustion is a greater risk to availability of the app. +Blazor clients establish a single connection per session and keep the connection open for as long as the browser window is open. Given the persistent nature of the connections and the stateful nature of server-side Blazor apps, connection exhaustion is a greater risk to availability of the app. -By default, there's no limit on the number of connections per user for a Blazor Server app. If the app requires a connection limit, take one or more of the following approaches: +By default, there's no limit on the number of connections per user for an app. If the app requires a connection limit, take one or more of the following approaches: :::moniker range=">= aspnetcore-5.0" @@ -98,10 +100,10 @@ By default, there's no limit on the number of connections per user for a Blazor * Require authentication to connect to the app and keep track of the active sessions per user. * Reject new sessions upon reaching a limit. * Proxy WebSocket connections to an app through the use of a proxy, such as the [Azure SignalR Service](/azure/azure-signalr/signalr-overview) that multiplexes connections from clients to an app. This provides an app with greater connection capacity than a single client can establish, preventing a client from exhausting the connections to the server. - * At the server level: Use a proxy/gateway in front of the app. For example, [Azure Front Door](/azure/frontdoor/front-door-overview) enables you to define, manage, and monitor the global routing of web traffic to an app and works when Blazor Server apps are configured to use Long Polling. + * At the server level: Use a proxy/gateway in front of the app. For example, [Azure Front Door](/azure/frontdoor/front-door-overview) enables you to define, manage, and monitor the global routing of web traffic to an app and works when apps are configured to use Long Polling. > [!NOTE] - > Although Long Polling is supported for Blazor Server apps, [WebSockets is the recommended transport protocol](xref:blazor/host-and-deploy/server#azure-signalr-service). As of February, 2023, [Azure Front Door](/azure/frontdoor/front-door-overview) doesn't support WebSockets, but support for WebSockets is under development for a future release of the service. For more information, see [Support WebSocket connections on Azure Front Door](https://feedback.azure.com/d365community/idea/c8b1d257-8a26-ec11-b6e6-000d3a4f0789). + > Although Long Polling is supported, [WebSockets is the recommended transport protocol](xref:blazor/host-and-deploy/server#azure-signalr-service). As of February, 2023, [Azure Front Door](/azure/frontdoor/front-door-overview) doesn't support WebSockets, but support for WebSockets is under development for a future release of the service. For more information, see [Support WebSocket connections on Azure Front Door](https://feedback.azure.com/d365community/idea/c8b1d257-8a26-ec11-b6e6-000d3a4f0789). :::moniker-end @@ -117,13 +119,13 @@ By default, there's no limit on the number of connections per user for a Blazor * At the server level: Use a proxy/gateway in front of the app. > [!NOTE] - > Although Long Polling is supported for Blazor Server apps, [WebSockets is the recommended transport protocol](xref:blazor/host-and-deploy/server#azure-signalr-service). + > Although Long Polling is supported, [WebSockets is the recommended transport protocol](xref:blazor/host-and-deploy/server#azure-signalr-service). :::moniker-end ## Denial of service (DoS) attacks -[Denial of service (DoS) attacks](https://developer.mozilla.org/docs/Glossary/DOS_attack) involve a client causing the server to exhaust one or more of its resources making the app unavailable. Blazor Server apps include default limits and rely on other ASP.NET Core and SignalR limits that are set on to protect against DoS attacks: +[Denial of service (DoS) attacks](https://developer.mozilla.org/docs/Glossary/DOS_attack) involve a client causing the server to exhaust one or more of its resources making the app unavailable. Blazor apps include default limits and rely on other ASP.NET Core and SignalR limits that are set on to protect against DoS attacks: * * @@ -169,14 +171,14 @@ Don't trust calls from JavaScript to .NET methods. When a .NET method is exposed * Ensure that the user has permission to perform the action requested. * Don't allocate an excessive quantity of resources as part of the .NET method invocation. For example, perform checks and place limits on CPU and memory use. * Take into account that static and instance methods can be exposed to JavaScript clients. Avoid sharing state across sessions unless the design calls for sharing state with appropriate constraints. - * For instance methods exposed through objects that are originally created through dependency injection (DI), the objects should be registered as scoped objects. This applies to any DI service that the Blazor Server app uses. + * For instance methods exposed through objects that are originally created through dependency injection (DI), the objects should be registered as scoped objects. This applies to any DI service that the app uses. * For static methods, avoid establishing state that can't be scoped to the client unless the app is explicitly sharing state by-design across all users on a server instance. * Avoid passing user-supplied data in parameters to JavaScript calls. If passing data in parameters is absolutely required, ensure that the JavaScript code handles passing the data without introducing [Cross-site scripting (XSS)](#cross-site-scripting-xss) vulnerabilities. For example, don't write user-supplied data to the DOM by setting the `innerHTML` property of an element. Consider using [Content Security Policy (CSP)](https://developer.mozilla.org/docs/Web/HTTP/CSP) to disable `eval` and other unsafe JavaScript primitives. For more information, see . * Avoid implementing custom dispatching of .NET invocations on top of the framework's dispatching implementation. Exposing .NET methods to the browser is an advanced scenario, not recommended for general Blazor development. ### Events -Events provide an entry point to a Blazor Server app. The same rules for safeguarding endpoints in web apps apply to event handling in Blazor Server apps. A malicious client can send any data it wishes to send as the payload for an event. +Events provide an entry point to an app. The same rules for safeguarding endpoints in web apps apply to event handling in Blazor apps. A malicious client can send any data it wishes to send as the payload for an event. For example: @@ -185,7 +187,7 @@ For example: The app must validate the data for any event that the app handles. The Blazor framework [forms components](xref:blazor/forms-and-input-components) perform basic validations. If the app uses custom forms components, custom code must be written to validate event data as appropriate. -Blazor Server events are asynchronous, so multiple events can be dispatched to the server before the app has time to react by producing a new render. This has some security implications to consider. Limiting client actions in the app must be performed inside event handlers and not depend on the current rendered view state. +Events are asynchronous, so multiple events can be dispatched to the server before the app has time to react by producing a new render. This has some security implications to consider. Limiting client actions in the app must be performed inside event handlers and not depend on the current rendered view state. Consider a counter component that should allow a user to increment a counter a maximum of three times. The button to increment the counter is conditionally based on the value of `count`: @@ -295,11 +297,11 @@ In addition to using a guard as described in the [Guard against multiple dispatc ### Avoid events that produce large amounts of data -Some DOM events, such as `oninput` or `onscroll`, can produce a large amount of data. Avoid using these events in Blazor server apps. +Some DOM events, such as `oninput` or `onscroll`, can produce a large amount of data. Avoid using these events in server-side Blazor server. ## Additional security guidance -The guidance for securing ASP.NET Core apps apply to Blazor Server apps and are covered in the following sections of this article: +The guidance for securing ASP.NET Core apps apply to server-side Blazor apps and are covered in the following sections of this article: * [Logging and sensitive data](#logging-and-sensitive-data) * [Protect information in transit with HTTPS](#protect-information-in-transit-with-https) @@ -321,9 +323,9 @@ The client-side error doesn't include the call stack and doesn't provide detail ### Protect information in transit with HTTPS -Blazor Server uses SignalR for communication between the client and the server. Blazor Server normally uses the transport that SignalR negotiates, which is typically WebSockets. +Blazor uses SignalR for communication between the client and the server. Blazor normally uses the transport that SignalR negotiates, which is typically WebSockets. -Blazor Server doesn't ensure the integrity and confidentiality of the data sent between the server and the client. Always use HTTPS. +Blazor doesn't ensure the integrity and confidentiality of the data sent between the server and the client. Always use HTTPS. ### Cross-site scripting (XSS) @@ -336,7 +338,7 @@ Cross-site scripting (XSS) allows an unauthorized party to execute arbitrary log * Modify the response of interop calls from .NET to JavaScript. * Avoid dispatching .NET to JS interop results. -The Blazor Server framework takes steps to protect against some of the preceding threats: +The Blazor framework takes steps to protect against some of the preceding threats: * Stops producing new UI updates if the client isn't acknowledging render batches. Configured with . * Times out any .NET to JavaScript call after one minute without receiving a response from the client. Configured with . @@ -360,7 +362,7 @@ In addition to the safeguards that the framework implements, the app must be cod * Don't trust the input on JS interop calls in either direction between JavaScript and .NET methods. * The app is responsible for validating that the content of arguments and results are valid, even if the arguments or results are correctly deserialized. -For a XSS vulnerability to exist, the app must incorporate user input in the rendered page. Blazor Server components execute a compile-time step where the markup in a `.razor` file is transformed into procedural C# logic. At runtime, the C# logic builds a *render tree* describing the elements, text, and child components. This is applied to the browser's DOM via a sequence of JavaScript instructions (or is serialized to HTML in the case of prerendering): +For a XSS vulnerability to exist, the app must incorporate user input in the rendered page. Blazor executes a compile-time step where the markup in a `.razor` file is transformed into procedural C# logic. At runtime, the C# logic builds a *render tree* describing the elements, text, and child components. This is applied to the browser's DOM via a sequence of JavaScript instructions (or is serialized to HTML in the case of prerendering): * User input rendered via normal Razor syntax (for example, `@someStringValue`) doesn't expose a XSS vulnerability because the Razor syntax is added to the DOM via commands that can only write text. Even if the value includes HTML markup, the value is displayed as static text. When prerendering, the output is HTML-encoded, which also displays the content as static text. * Script tags aren't allowed and shouldn't be included in the app's component render tree. If a script tag is included in a component's markup, a compile-time error is generated. @@ -372,10 +374,10 @@ For more information, see . ### Cross-origin protection -Cross-origin attacks involve a client from a different origin performing an action against the server. The malicious action is typically a GET request or a form POST (Cross-Site Request Forgery, CSRF), but opening a malicious WebSocket is also possible. Blazor Server apps offer [the same guarantees that any other SignalR app using the hub protocol offer](xref:signalr/security): +Cross-origin attacks involve a client from a different origin performing an action against the server. The malicious action is typically a GET request or a form POST (Cross-Site Request Forgery, CSRF), but opening a malicious WebSocket is also possible. Blazor apps offer [the same guarantees that any other SignalR app using the hub protocol offer](xref:signalr/security): -* Blazor Server apps can be accessed cross-origin unless additional measures are taken to prevent it. To disable cross-origin access, either disable CORS in the endpoint by adding the CORS Middleware to the pipeline and adding the to the Blazor endpoint metadata or limit the set of allowed origins by [configuring SignalR for Cross-Origin Resource Sharing](xref:signalr/security#cross-origin-resource-sharing). For guidance on WebSocket origin restrictions, see . -* If CORS is enabled, extra steps might be required to protect the app depending on the CORS configuration. If CORS is globally enabled, CORS can be disabled for the Blazor Server hub by adding the metadata to the endpoint metadata after calling on the endpoint route builder. +* Apps can be accessed cross-origin unless additional measures are taken to prevent it. To disable cross-origin access, either disable CORS in the endpoint by adding the CORS Middleware to the pipeline and adding the to the Blazor endpoint metadata or limit the set of allowed origins by [configuring SignalR for Cross-Origin Resource Sharing](xref:signalr/security#cross-origin-resource-sharing). For guidance on WebSocket origin restrictions, see . +* If CORS is enabled, extra steps might be required to protect the app depending on the CORS configuration. If CORS is globally enabled, CORS can be disabled for the Blazor SignalR hub by adding the metadata to the endpoint metadata after calling on the endpoint route builder. For more information, see . @@ -392,7 +394,7 @@ For more information, see the following resources: ### Open redirects -When a Blazor Server app session starts, the server performs basic validation of the URLs sent as part of starting the session. The framework checks that the base URL is a parent of the current URL before establishing the circuit. No additional checks are performed by the framework. +When an app session starts, the server performs basic validation of the URLs sent as part of starting the session. The framework checks that the base URL is a parent of the current URL before establishing the circuit. No additional checks are performed by the framework. When a user selects a link on the client, the URL for the link is sent to the server, which determines what action to take. For example, the app may perform a client-side navigation or indicate to the browser to go to the new location. diff --git a/aspnetcore/blazor/security/webassembly/additional-scenarios.md b/aspnetcore/blazor/security/webassembly/additional-scenarios.md index af2184caad4d..95dc0c826ab7 100644 --- a/aspnetcore/blazor/security/webassembly/additional-scenarios.md +++ b/aspnetcore/blazor/security/webassembly/additional-scenarios.md @@ -47,8 +47,12 @@ builder.Services.AddScoped(sp => sp.GetRequiredService() .CreateClient("WebAPI")); ``` +:::moniker range="< aspnetcore-8.0" + For a hosted Blazor [solution](xref:blazor/tooling#visual-studio-solution-file-sln) based on the [Blazor WebAssembly project template](xref:blazor/project-structure), request URIs are within the app's base URI by default. Therefore, (`new Uri(builder.HostEnvironment.BaseAddress)`) is assigned to the in an app generated from the project template. +:::moniker-end + The configured is used to make authorized requests using the [`try-catch`](/dotnet/csharp/language-reference/keywords/try-catch) pattern: ```razor @@ -268,7 +272,7 @@ public class CustomAuthorizationMessageHandler : AuthorizationMessageHandler In the preceding code, the scopes `example.read` and `example.write` are generic examples not meant to reflect valid scopes for any particular provider. -In `Program.cs`, `CustomAuthorizationMessageHandler` is registered as a transient service and is configured as the for outgoing instances made by a named . +In the `Program` file, `CustomAuthorizationMessageHandler` is registered as a transient service and is configured as the for outgoing instances made by a named . In the following example, is an extension in . Add the package to an app that doesn't already reference it. @@ -288,8 +292,12 @@ builder.Services.AddHttpClient("WebAPI", > * [Utility base component classes to manage a DI scope](xref:blazor/fundamentals/dependency-injection#utility-base-component-classes-to-manage-a-di-scope) > * [Detect client-side transient disposables](xref:blazor/fundamentals/dependency-injection#detect-client-side-transient-disposables) +:::moniker range="< aspnetcore-8.0" + For a hosted Blazor solution based on the [Blazor WebAssembly project template](xref:blazor/project-structure), (`new Uri(builder.HostEnvironment.BaseAddress)`) is assigned to the by default. +:::moniker-end + The configured is used to make authorized requests using the [`try-catch`](/dotnet/csharp/language-reference/keywords/try-catch) pattern. Where the client is created with ([`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) package), the is supplied instances that include access tokens when making requests to the server API. If the request URI is a relative URI, as it is in the following example (`ExampleAPIMethod`), it's combined with the when the client app makes the request: ```razor @@ -321,7 +329,7 @@ The configured is used to make authorized requ can be configured with authorized URLs, scopes, and a return URL using the method. configures the handler to authorize outbound HTTP requests using an access token. The access token is only attached if at least one of the authorized URLs is a base of the request URI (). If the request URI is a relative URI, it's combined with the . -In the following example, configures an in `Program.cs`: +In the following example, configures an in the `Program` file: ```csharp using System.Net.Http; @@ -341,11 +349,15 @@ builder.Services.AddScoped(sp => new HttpClient( In the preceding code, the scopes `example.read` and `example.write` are generic examples not meant to reflect valid scopes for any particular provider. +:::moniker range="< aspnetcore-8.0" + For a hosted Blazor solution based on the [Blazor WebAssembly project template](xref:blazor/project-structure), is assigned to the following by default: * The (`new Uri(builder.HostEnvironment.BaseAddress)`). * A URL of the `authorizedUrls` array. +:::moniker-end + ## Typed `HttpClient` A typed client can be defined that handles all of the HTTP and token acquisition concerns within a single class. @@ -432,7 +444,7 @@ In the following example, ( .AddHttpMessageHandler(); ``` +:::moniker range="< aspnetcore-8.0" + For a hosted Blazor solution based on the [Blazor WebAssembly project template](xref:blazor/project-structure), (`new Uri(builder.HostEnvironment.BaseAddress)`) is assigned to the by default. -`FetchData` component (`Pages/FetchData.razor`): +:::moniker-end + +In a component that fetches weather data: ```razor @inject WeatherForecastClient Client @@ -468,7 +484,7 @@ In the following example, ( @@ -481,11 +497,15 @@ builder.Services.AddHttpClient( In the preceding code, the scopes `example.read` and `example.write` are generic examples not meant to reflect valid scopes for any particular provider. +:::moniker range="< aspnetcore-8.0" + For a hosted Blazor solution based on the [Blazor WebAssembly project template](xref:blazor/project-structure), is assigned to the following by default: * The (`new Uri(builder.HostEnvironment.BaseAddress)`). * A URL of the `authorizedUrls` array. +:::moniker-end + ## Unauthenticated or unauthorized web API requests in an app with a secure default client An app that ordinarily uses a secure default can also make unauthenticated or unauthorized web API requests by configuring a named . @@ -494,15 +514,19 @@ In the following example, client.BaseAddress = new Uri("https://www.example.com/base")); ``` +:::moniker range="< aspnetcore-8.0" + For a hosted Blazor solution based on the [Blazor WebAssembly project template](xref:blazor/project-structure), (`new Uri(builder.HostEnvironment.BaseAddress)`) is assigned to the by default. +:::moniker-end + The preceding registration is in addition to the existing secure default registration. A component creates the from the ([`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) package) to make unauthenticated or unauthorized requests: @@ -536,7 +560,7 @@ An alternative approach to using the i Access tokens can be manually obtained by calling . In the following example, an additional scope is required by an app for the default . The Microsoft Authentication Library (MSAL) example configures the scope with `MsalProviderOptions`: -In `Program.cs`: +In the `Program` file: ```csharp builder.Services.AddMsalAuthentication(options => @@ -599,8 +623,12 @@ app.UseCors(policy => .AllowCredentials()); ``` +:::moniker range="< aspnetcore-8.0" + A hosted Blazor solution based on the [Blazor WebAssembly project template](xref:blazor/project-structure) uses the same base address for the client and server apps. The client app's is set to a URI of `builder.HostEnvironment.BaseAddress` by default. CORS configuration is ***not*** required in the default configuration of a hosted Blazor solution. Additional client apps that aren't hosted by the server project and don't share the server app's base address ***do*** require CORS configuration in the server project. +:::moniker-end + For more information, see and the sample app's HTTP Request Tester component (`Components/HTTPRequestTester.razor`). ## Handle token request errors @@ -778,7 +806,7 @@ The following example shows how to: During an authentication operation, there are cases where you want to save the app state before the browser is redirected to the IP. This can be the case when you're using a state container and want to restore the state after the authentication succeeds. You can use a custom authentication state object to preserve app-specific state or a reference to it and restore that state after the authentication operation successfully completes. The following example demonstrates the approach. -A state container class is created in the app with properties to hold the app's state values. In the following example, the container is used to maintain the counter value of the default [Blazor project template's](xref:blazor/project-structure) `Counter` component (`Pages/Counter.razor`). Methods for serializing and deserializing the container are based on . +A state container class is created in the app with properties to hold the app's state values. In the following example, the container is used to maintain the counter value of the default [Blazor project template's](xref:blazor/project-structure) `Counter` component (`Counter.razor`). Methods for serializing and deserializing the container are based on . ```csharp using System.Text.Json; @@ -863,7 +891,7 @@ public class ApplicationAuthenticationState : RemoteAuthenticationState :::moniker-end -The `Authentication` component (`Pages/Authentication.razor`) saves and restores the app's state using local session storage with the `StateContainer` serialization and deserialization methods, `GetStateForLocalStorage` and `SetStateFromLocalStorage`: +The `Authentication` component (`Authentication.razor`) saves and restores the app's state using local session storage with the `StateContainer` serialization and deserialization methods, `GetStateForLocalStorage` and `SetStateFromLocalStorage`: :::moniker range=">= aspnetcore-6.0" @@ -973,7 +1001,7 @@ The `Authentication` component (`Pages/Authentication.razor`) saves and restores :::moniker-end -This example uses Microsoft Entra (ME-ID) for authentication. In `Program.cs`: +This example uses Microsoft Entra (ME-ID) for authentication. In the `Program` file: * The `ApplicationAuthenticationState` is configured as the Microsoft Authentication Library (MSAL) `RemoteAuthenticationState` type. * The state container is registered in the service container. @@ -1007,7 +1035,7 @@ The routes shown in the preceding table are configurable via { @@ -1071,7 +1099,7 @@ You're allowed to break the UI into different pages if you choose to do so. includes a default set of UI fragments for each authentication state. Each state can be customized by passing in a custom . To customize the displayed text during the initial login process, can change the as follows. -`Authentication` component (`Pages/Authentication.razor`): +`Authentication` component (`Authentication.razor`): :::moniker range=">= aspnetcore-6.0" @@ -1303,6 +1331,8 @@ Register the `CustomAccountFactory` for the authentication provider in use. Any For an additional example that works with ME-ID security groups and ME-ID Administrator Roles and a custom user account class, see . +:::moniker range="< aspnetcore-8.0" + ## Prerendering with authentication Prerendering content that requires authentication and authorization isn't currently supported. After following the guidance in one of the Blazor WebAssembly security app topics, use the following instructions to create an app that: @@ -1310,7 +1340,7 @@ Prerendering content that requires authentication and authorization isn't curren * Prerenders paths for which authorization isn't required. * Doesn't prerender paths for which authorization is required. -For the **:::no-loc text="Client":::** project's `Program.cs` file, factor common service registrations into a separate method (for example, create a `ConfigureCommonServices` method in the **:::no-loc text="Client":::** project). Common services are those that the developer registers for use by both the client and server projects. +For the **:::no-loc text="Client":::** project's the `Program` file file, factor common service registrations into a separate method (for example, create a `ConfigureCommonServices` method in the **:::no-loc text="Client":::** project). Common services are those that the developer registers for use by both the client and server projects. ```csharp public static void ConfigureCommonServices(IServiceCollection services) @@ -1319,7 +1349,7 @@ public static void ConfigureCommonServices(IServiceCollection services) } ``` -`Program.cs`: +In the `Program` file: ```csharp var builder = WebAssemblyHostBuilder.CreateDefault(args); @@ -1332,9 +1362,11 @@ ConfigureCommonServices(builder.Services); await builder.Build().RunAsync(); ``` -:::moniker range=">= aspnetcore-6.0" +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" -In the **:::no-loc text="Server":::** project's `Program.cs` file, register the following additional services and call `ConfigureCommonServices`: +In the **:::no-loc text="Server":::** project's the `Program` file file, register the following additional services and call `ConfigureCommonServices`: ```csharp using Microsoft.AspNetCore.Components.Authorization; @@ -1376,6 +1408,8 @@ public void ConfigureServices(IServiceCollection services) :::moniker-end +:::moniker range="< aspnetcore-8.0" + For more information on the Blazor framework server authentication provider (`ServerAuthenticationStateProvider`), see . In the **:::no-loc text="Server":::** project's `Pages/_Host.cshtml` file, replace the `Component` Tag Helper (``) with the following: @@ -1402,12 +1436,18 @@ In the preceding example: * Avoids prerendering (`render-mode="WebAssembly"`) for authentication paths. * Prerenders (`render-mode="WebAssemblyPrerendered"`) for non-authentication paths. +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + ## Options for hosted apps and third-party login providers When authenticating and authorizing a hosted Blazor WebAssembly app with a third-party provider, there are several options available for authenticating the user. Which one you choose depends on your scenario. For more information, see . +:::moniker-end + ### Authenticate users to only call protected third party APIs Authenticate the user with a client-side OAuth flow against the third-party API provider: @@ -1478,12 +1518,18 @@ If tacking on a segment to the authority isn't appropriate for the app's OIDC pr The list of claims in the ID token changes for v2.0 endpoints. For more information, see [Why update to Microsoft identity platform (v2.0)?](/azure/active-directory/azuread-dev/azure-ad-endpoint-comparison). +:::moniker range="< aspnetcore-8.0" + + + ## Configure and use gRPC in components To configure a Blazor WebAssembly app to use the [ASP.NET Core gRPC framework](xref:grpc/index): * Enable gRPC-Web on the server. For more information, see . -* Register gRPC services for the app's message handler. The following example configures the app's authorization message handler to use the [`GreeterClient` service from the gRPC tutorial](xref:tutorials/grpc/grpc-start#create-a-grpc-service) (`Program.cs`): +* Register gRPC services for the app's message handler. The following example configures the app's authorization message handler to use the [`GreeterClient` service from the gRPC tutorial](xref:tutorials/grpc/grpc-start#create-a-grpc-service) (the `Program` file): ```csharp using System.Net.Http; @@ -1510,9 +1556,11 @@ builder.Services.AddScoped(sp => The placeholder `{ASSEMBLY NAME}` is the app's assembly name (for example, `BlazorSample`). Place the `.proto` file in the `Shared` project of the hosted Blazor solution. -A component in the client app can make gRPC calls using the gRPC client (`Pages/Grpc.razor`): +A component in the client app can make gRPC calls using the gRPC client (`Grpc.razor`): -:::moniker range=">= aspnetcore-6.0" +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-8.0" ```razor @page "/grpc" @@ -1596,10 +1644,14 @@ Server response: @serverResponse :::moniker-end +:::moniker range="< aspnetcore-8.0" + The placeholder `{ASSEMBLY NAME}` is the app's assembly name (for example, `BlazorSample`). To use the `Status.DebugException` property, use [`Grpc.Net.Client`](https://www.nuget.org/packages/Grpc.Net.Client) version 2.30.0 or later. For more information, see . +:::moniker-end + ## Replace the `AuthenticationService` implementation The following subsections explain how to replace: diff --git a/aspnetcore/blazor/security/webassembly/graph-api.md b/aspnetcore/blazor/security/webassembly/graph-api.md index b2ede25a9997..ea93e58d12ae 100644 --- a/aspnetcore/blazor/security/webassembly/graph-api.md +++ b/aspnetcore/blazor/security/webassembly/graph-api.md @@ -26,8 +26,12 @@ The guidance in this article isn't meant to replace the primary [Microsoft Graph > [!IMPORTANT] > The scenarios described in this article apply to using Microsoft Entra (ME-ID) as the identity provider, not AAD B2C. Using Microsoft Graph with a client-side Blazor WebAssembly app and the AAD B2C identity provider isn't supported at this time. +:::moniker range="< aspnetcore-8.0" + Using a hosted Blazor WebAssembly app is supported, where the **:::no-loc text="Server":::** app uses the Graph SDK/API to provide Graph data to the **:::no-loc text="Client":::** app via web API. For more information, see the [Hosted Blazor WebAssembly solutions](#hosted-blazor-webassembly-solutions) section of this article. +:::moniker-end + The examples in this article take advantage of recent .NET features released with ASP.NET Core 6.0 or later. When using the examples in ASP.NET Core 5.0 or earlier, minor modifications are required. However, the text and code examples that pertain to interacting with Microsoft Graph are the same for all versions of ASP.NET Core. :::zone pivot="graph-sdk-5" @@ -36,8 +40,18 @@ The examples in this article take advantage of recent .NET features released wit The Microsoft Graph SDK for use in Blazor apps is called the *Microsoft Graph .NET Client Library*. +:::moniker range=">= aspnetcore-8.0" + +The Graph SDK examples require the following package references in the standalone Blazor WebAssembly app: + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + The Graph SDK examples require the following package references in the standalone Blazor WebAssembly app or the **:::no-loc text="Client":::** app of a hosted Blazor WebAssembly solution: +:::moniker-end + * [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) * [`Microsoft.Graph`](https://www.nuget.org/packages/Microsoft.Graph) @@ -54,8 +68,18 @@ After adding the Microsoft Graph API scopes in the ME-ID area of the Azure porta } ``` +:::moniker range=">= aspnetcore-8.0" + +Add the following `GraphClientExtensions` class to the standalone app. The scopes are provided to the property of the in the `AuthenticateRequestAsync` method. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + Add the following `GraphClientExtensions` class to the standalone app or **:::no-loc text="Client":::** app of a hosted Blazor WebAssembly [solution](xref:blazor/tooling#visual-studio-solution-file-sln). The scopes are provided to the property of the in the `AuthenticateRequestAsync` method. +:::moniker-end + When an access token isn't obtained, the following code doesn't set a Bearer authorization header for Graph requests. `GraphClientExtensions.cs`: @@ -135,7 +159,7 @@ internal static class GraphClientExtensions } ``` -In `Program.cs`, add the Graph client services and configuration with the `AddGraphClient` extension method: +In the `Program` file, add the Graph client services and configuration with the `AddGraphClient` extension method: ```csharp var baseUrl = builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"]; @@ -149,7 +173,7 @@ builder.Services.AddGraphClient(baseUrl, scopes); The following `GraphExample` component uses an injected `GraphServiceClient` to obtain the user's ME-ID profile data and display their mobile phone number. For any test user that you create in ME-ID, make sure that you give the user's ME-ID profile a mobile phone number in the Azure portal. -`Pages/GraphExample.razor`: +`GraphExample.razor`: ```razor @page "/graph-example" @@ -260,13 +284,13 @@ public class CustomAccountFactory Configure the MSAL authentication to use the custom user account factory. -Confirm that the `Program.cs` file uses the namespace: +Confirm that the the `Program` file file uses the namespace: ```csharp using Microsoft.AspNetCore.Components.WebAssembly.Authentication; ``` -The example in this section builds on the approach of reading the base URL with version and scopes from app configuration via the `MicrosoftGraph` section in `wwwroot/appsettings.json` file. The following lines should already be present in `Program.cs` from following the guidance earlier in this article: +The example in this section builds on the approach of reading the base URL with version and scopes from app configuration via the `MicrosoftGraph` section in `wwwroot/appsettings.json` file. The following lines should already be present in the `Program` file from following the guidance earlier in this article: ```csharp var baseUrl = builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"]; @@ -276,7 +300,7 @@ var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes") builder.Services.AddGraphClient(baseUrl, scopes); ``` -In `Program.cs`, find the call to the extension method. Update the code to the following, which includes a call to that adds an account claims principal factory with the `CustomAccountFactory`. +In the `Program` file, find the call to the extension method. Update the code to the following, which includes a call to that adds an account claims principal factory with the `CustomAccountFactory`. If the app uses a custom user account class that extends , swap the custom user account class for in the following code. @@ -293,7 +317,7 @@ builder.Services.AddMsalAuthentication property of the in the `AuthenticateRequestAsync` method. The is extended from the default value of 100 seconds to 300 seconds to give the `HttpClient` more time to receive a response from Microsoft Graph. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + Add the following `GraphClientExtensions` class to the standalone app or **:::no-loc text="Client":::** app of a hosted Blazor WebAssembly [solution](xref:blazor/tooling#visual-studio-solution-file-sln). The scopes are provided to the property of the in the `AuthenticateRequestAsync` method. The is extended from the default value of 100 seconds to 300 seconds to give the `HttpClient` more time to receive a response from Microsoft Graph. +:::moniker-end + When an access token isn't obtained, the following code doesn't set a Bearer authorization header for Graph requests. `GraphClientExtensions.cs`: @@ -468,7 +512,7 @@ internal static class GraphClientExtensions } ``` -In `Program.cs`, add the Graph client services and configuration with the `AddGraphClient` extension method: +In the `Program` file, add the Graph client services and configuration with the `AddGraphClient` extension method: ```csharp var baseUrl = builder.Configuration @@ -483,7 +527,7 @@ builder.Services.AddGraphClient(baseUrl, scopes); The following `GraphExample` component uses an injected `GraphServiceClient` to obtain the user's ME-ID profile data and display their mobile phone number. For any test user that you create in ME-ID, make sure that you give the user's ME-ID profile a mobile phone number in the Azure portal. -`Pages/GraphExample.razor`: +`GraphExample.razor`: ```razor @page "/graph-example" @@ -587,13 +631,13 @@ public class CustomAccountFactory Configure the MSAL authentication to use the custom user account factory. -Confirm that the `Program.cs` file uses the namespace: +Confirm that the the `Program` file file uses the namespace: ```csharp using Microsoft.AspNetCore.Components.WebAssembly.Authentication; ``` -The example in this section builds on the approach of reading the base URL with version and scopes from app configuration via the `MicrosoftGraph` section in `wwwroot/appsettings.json` file. The following lines should already be present in `Program.cs` from following the guidance earlier in this article: +The example in this section builds on the approach of reading the base URL with version and scopes from app configuration via the `MicrosoftGraph` section in `wwwroot/appsettings.json` file. The following lines should already be present in the `Program` file from following the guidance earlier in this article: ```csharp var baseUrl = string.Join("/", @@ -604,7 +648,7 @@ var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes") builder.Services.AddGraphClient(baseUrl, scopes); ``` -In `Program.cs`, find the call to the extension method. Update the code to the following, which includes a call to that adds an account claims principal factory with the `CustomAccountFactory`. +In the `Program` file, find the call to the extension method. Update the code to the following, which includes a call to that adds an account claims principal factory with the `CustomAccountFactory`. If the app uses a custom user account class that extends , swap the custom user account class for in the following code. @@ -621,7 +665,7 @@ builder.Services.AddMsalAuthentication for Graph API calls to obtain a user's mobile phone number to process a call or to customize a user's claims to include a mobile phone number claim and an office location claim. +:::moniker range=">= aspnetcore-8.0" + +The examples require a package reference for [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) for the standalone Blazor WebAssembly app. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + The examples require a package reference for [`Microsoft.Extensions.Http`](https://www.nuget.org/packages/Microsoft.Extensions.Http) for the standalone Blazor WebAssembly app or the **:::no-loc text="Client":::** app of a hosted Blazor WebAssembly solution. +:::moniker-end + [!INCLUDE[](~/includes/package-reference.md)] After adding the Microsoft Graph API scopes in the ME-ID area of the Azure portal, add the following app settings configuration to the `wwwroot/appsettings.json` file. In the following example, the `User.Read` scope is specified to match the examples in later sections of this article. @@ -683,7 +737,7 @@ After adding the Microsoft Graph API scopes in the ME-ID area of the Azure porta } ``` -Create the following `GraphAuthorizationMessageHandler` class and project configuration in `Program.cs` for working with Graph API. The base URL and scopes are provided to the handler from configuration. +Create the following `GraphAuthorizationMessageHandler` class and project configuration in the `Program` file for working with Graph API. The base URL and scopes are provided to the handler from configuration. `GraphAuthorizationMessageHandler.cs`: @@ -704,7 +758,7 @@ public class GraphAuthorizationMessageHandler : AuthorizationMessageHandler } ``` -In `Program.cs`, configure the named for Graph API: +In the `Program` file, configure the named for Graph API: ```csharp builder.Services.AddTransient(); @@ -741,7 +795,7 @@ public class UserInfo In the following `GraphExample` component, an is created for Graph API to issue a request for the user's profile data. The `me` resource (`/me`) are added to the base URL with version for the Graph API request. JSON data returned by Graph is deserialized into the `UserInfo` class properties. In the following example, the mobile phone number is obtained. You can add similar code to include the user's ME-ID profile office location if you wish (`userInfo.OfficeLocation`). If the access token request fails, the user is redirected to sign into the app for a new access token. -`Pages/GraphExample.razor`: +`GraphExample.razor`: ```razor @page "/graph-example" @@ -868,13 +922,13 @@ public class CustomAccountFactory } ``` -The MSAL authentication is configured to use the custom user account factory. Start by confirming that the `Program.cs` file uses the namespace: +The MSAL authentication is configured to use the custom user account factory. Start by confirming that the the `Program` file file uses the namespace: ```csharp using Microsoft.AspNetCore.Components.WebAssembly.Authentication; ``` -In `Program.cs`, find the call to the extension method. Update the code to the following, which includes a call to that adds an account claims principal factory with the `CustomAccountFactory`. +In the `Program` file, find the call to the extension method. Update the code to the following, which includes a call to that adds an account claims principal factory with the `CustomAccountFactory`. If the app uses a custom user account class that extends , swap your app's custom user account class for in the following code. @@ -893,7 +947,7 @@ The preceding example is for an app that uses ME-ID authentication with MSAL. Si You can use the following `UserClaims` component to study the user's claims after the user authenticates with ME-ID: -`Pages/UserClaims.razor`: +`UserClaims.razor`: ```razor @page "/user-claims" @@ -936,6 +990,8 @@ When testing with the Graph API locally, we recommend using a new in-private/inc :::zone-end +:::moniker range="< aspnetcore-8.0" + ## Hosted Blazor WebAssembly solutions The examples in this article pertain to using the Graph SDK or a named `HttpClient` with Graph API directly from a standalone Blazor WebAssembly app or directly from the **:::no-loc text="Client":::** app of a hosted Blazor WebAssembly [solution](xref:blazor/tooling#visual-studio-solution-file-sln). An additional scenario that isn't covered by this article is for a **:::no-loc text="Client":::** app of a hosted solution to call the **:::no-loc text="Server":::** app of the solution via web API, and then the the **:::no-loc text="Server":::** app uses the Graph SDK/API to call Microsoft Graph and return data to the **:::no-loc text="Client":::** app. Although this is a supported approach, it isn't covered by this article. If you wish to adopt this approach: @@ -943,14 +999,28 @@ The examples in this article pertain to using the Graph SDK or a named `HttpClie * Follow the guidance in for the web API aspects on issuing requests to the **:::no-loc text="Server":::** app from the **:::no-loc text="Client":::** app and returning data to the **:::no-loc text="Client":::** app. * Follow the guidance in the primary [Microsoft Graph documentation](/graph/) to use the Graph SDK with a typical ASP.NET Core app, which in this scenario is the **:::no-loc text="Server":::** app of the solution. If you use the Blazor WebAssembly project template to the create the hosted Blazor WebAssembly solution (**ASP.NET Core Hosted**/`-h|--hosted`) with organizational authorization (single organization/`SingleOrg` or multiple organization/`MultiOrg`) and the Microsoft Graph option (**Microsoft identity platform** > **Connected Services** > **Add Microsoft Graph permissions** in Visual Studio or the `--calls-graph` option with the .NET CLI `dotnet new` command), the **:::no-loc text="Server":::** app of the solution is configured to use the Graph SDK when the solution is created from the project template. +:::moniker-end + ## Additional resources ### General guidance +:::moniker range=">= aspnetcore-8.0" + +* [Microsoft Graph documentation](/graph/) +* [Microsoft Graph sample Blazor WebAssembly app](https://github.com/microsoftgraph/msgraph-sample-blazor-clientside): This sample demonstrates how to use the Microsoft Graph .NET SDK to access data in Office 365 from Blazor WebAssembly apps. +* [Build .NET apps with Microsoft Graph tutorial](/graph/tutorials/dotnet?tabs=aad) and [Microsoft Graph sample ASP.NET Core app](https://github.com/microsoftgraph/msgraph-sample-aspnet-core/tree/main/): Although these resources don't directly apply to calling Graph from *client-side* Blazor WebAssembly apps, the ME-ID app configuration and Microsoft Graph coding practices in the linked resources are relevant for standalone Blazor WebAssembly apps and should be consulted for general best practices. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + * [Microsoft Graph documentation](/graph/) * [Microsoft Graph sample Blazor WebAssembly app](https://github.com/microsoftgraph/msgraph-sample-blazor-clientside): This sample demonstrates how to use the Microsoft Graph .NET SDK to access data in Office 365 from Blazor WebAssembly apps. * [Build .NET apps with Microsoft Graph tutorial](/graph/tutorials/dotnet?tabs=aad) and [Microsoft Graph sample ASP.NET Core app](https://github.com/microsoftgraph/msgraph-sample-aspnet-core/tree/main/): These resources are most appropriate for ***hosted*** Blazor WebAssembly solutions, where the **:::no-loc text="Server":::** app is configured to access Microsoft Graph as a typical ASP.NET Core app on behalf of the **:::no-loc text="Client":::** app. The **:::no-loc text="Client":::** app uses web API to make requests to the **:::no-loc text="Server":::** app for Graph data. Although these resources don't directly apply to calling Graph from *client-side* Blazor WebAssembly apps, the ME-ID app configuration and Microsoft Graph coding practices in the linked resources are relevant for standalone Blazor WebAssembly apps and should be consulted for general best practices. +:::moniker-end + ### Security guidance * [Microsoft Graph auth overview](/graph/auth/) diff --git a/aspnetcore/blazor/security/webassembly/hosted-with-azure-active-directory-b2c.md b/aspnetcore/blazor/security/webassembly/hosted-with-azure-active-directory-b2c.md index 34321efdc499..76aafd08ae8b 100644 --- a/aspnetcore/blazor/security/webassembly/hosted-with-azure-active-directory-b2c.md +++ b/aspnetcore/blazor/security/webassembly/hosted-with-azure-active-directory-b2c.md @@ -2,7 +2,7 @@ title: Secure a hosted ASP.NET Core Blazor WebAssembly app with Azure Active Directory B2C author: guardrex description: Learn how to secure a hosted ASP.NET Core Blazor WebAssembly app with Azure Active Directory B2C. -monikerRange: '>= aspnetcore-3.1' +monikerRange: '>= aspnetcore-3.1 < aspnetcore-8.0' ms.author: riande ms.custom: mvc ms.date: 04/25/2023 @@ -10,8 +10,6 @@ uid: blazor/security/webassembly/hosted-with-azure-active-directory-b2c --- # Secure a hosted ASP.NET Core Blazor WebAssembly app with Azure Active Directory B2C -[!INCLUDE[](~/includes/not-latest-version.md)] - This article explains how to create a [hosted Blazor WebAssembly solution](xref:blazor/hosting-models#blazor-webassembly) that uses [Azure Active Directory (AAD) B2C](/azure/active-directory-b2c/overview) for authentication. For additional security scenario coverage after reading this article, see . @@ -143,13 +141,13 @@ By default, the **:::no-loc text="Server":::** app API populates `User.Identity. To configure the app to receive the value from the `name` claim type: -* Add a namespace for to `Program.cs`: +* Add a namespace for to the `Program` file: ```csharp using Microsoft.AspNetCore.Authentication.JwtBearer; ``` -* Configure the of the in `Program.cs`: +* Configure the of the in the `Program` file: ```csharp builder.Services.Configure( @@ -299,7 +297,7 @@ The [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages Support for instances is added that include access tokens when making requests to the server project. -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI", client => @@ -314,7 +312,7 @@ The placeholder `{PROJECT NAME}` is the project name at solution creation. For e Support for authenticating users is registered in the service container with the extension method provided by the [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages/Microsoft.Authentication.WebAssembly.Msal) package. This method sets up the services required for the app to interact with the Identity Provider (IP). -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddMsalAuthentication(options => diff --git a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md index 9a5ce216bfda..6a2e5ab3bfd0 100644 --- a/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md +++ b/aspnetcore/blazor/security/webassembly/hosted-with-identity-server.md @@ -2,7 +2,7 @@ title: Secure a hosted ASP.NET Core Blazor WebAssembly app with Identity Server author: guardrex description: Learn how to secure a hosted ASP.NET Core Blazor WebAssembly app with Identity Server. -monikerRange: '>= aspnetcore-3.1' +monikerRange: '>= aspnetcore-3.1 < aspnetcore-8.0' ms.author: riande ms.custom: mvc ms.date: 03/23/2023 @@ -10,8 +10,6 @@ uid: blazor/security/webassembly/hosted-with-identity-server --- # Secure a hosted ASP.NET Core Blazor WebAssembly app with Identity Server -[!INCLUDE[](~/includes/not-latest-version.md)] - This article explains how to create a [hosted Blazor WebAssembly solution](xref:blazor/hosting-models#blazor-webassembly) that uses [Duende Identity Server](https://docs.duendesoftware.com) to authenticate users and API calls. :::moniker range=">= aspnetcore-6.0" @@ -101,7 +99,7 @@ The following services are registered. :::moniker range=">= aspnetcore-6.0" -* In `Program.cs`: +* In the `Program` file: * Entity Framework Core and ASP.NET Core Identity: @@ -171,7 +169,7 @@ The following services are registered. :::moniker range=">= aspnetcore-6.0" -* In `Program.cs`: +* In the `Program` file: :::moniker-end @@ -270,7 +268,7 @@ If adding authentication to an app, manually add the [`Microsoft.AspNetCore.Comp *This section pertains to the solution's **:::no-loc text="Client":::** app.* -In `Program.cs`, a named is configured to supply instances that include access tokens when making requests to the server API. By default at solution creation, the named is `{PROJECT NAME}.ServerAPI`, where the `{PROJECT NAME}` placeholder is the project's name. +In the `Program` file, a named is configured to supply instances that include access tokens when making requests to the server API. By default at solution creation, the named is `{PROJECT NAME}.ServerAPI`, where the `{PROJECT NAME}` placeholder is the project's name. ```csharp builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI", @@ -488,7 +486,7 @@ public class CustomUserFactory :::moniker-end -In the **:::no-loc text="Client":::** app, register the factory in `Program.cs`: +In the **:::no-loc text="Client":::** app, register the factory in the `Program` file: ```csharp builder.Services.AddApiAuthorization() @@ -499,7 +497,7 @@ In the **:::no-loc text="Server":::** app, call = aspnetcore-3.1' +monikerRange: '>= aspnetcore-3.1 < aspnetcore-8.0' ms.author: riande ms.custom: "devx-track-csharp, mvc" ms.date: 04/25/2023 @@ -10,13 +10,14 @@ uid: blazor/security/webassembly/hosted-with-microsoft-entra-id --- # Secure a hosted ASP.NET Core Blazor WebAssembly app with Microsoft Entra ID -[!INCLUDE[](~/includes/not-latest-version.md)] - This article explains how to create a [hosted Blazor WebAssembly solution](xref:blazor/hosting-models#blazor-webassembly) that uses [Microsoft Entra ID (ME-ID)](https://azure.microsoft.com/services/active-directory/) for authentication. This article focuses on a single tenant app with a single tenant Azure app registration. This article doesn't cover a *multi-tenant ME-ID registration*. For more information, see [Making your application multi-tenant](/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant). -This article focuses on the use of an **Microsoft Entra ID** tenant, as described in [Quickstart: Set up a tenant](/azure/active-directory/develop/quickstart-create-new-tenant). If the app is registered in an **Azure Active Directory B2C** tenant, as described in [Tutorial: Create an Azure Active Directory B2C tenant](/azure/active-directory-b2c/tutorial-create-tenant) but follows the guidance in this article, the App ID URI is managed differently by ME-ID. For more information, see the [Use of an Azure Active Directory B2C tenant](#use-of-an-azure-active-directory-b2c-tenant) section of this article. + + +This article focuses on the use of an **Azure Active Directory** tenant, as described in [Quickstart: Set up a tenant](/azure/active-directory/develop/quickstart-create-new-tenant). If the app is registered in an **Azure Active Directory B2C** tenant, as described in [Tutorial: Create an Azure Active Directory B2C tenant](/azure/active-directory-b2c/tutorial-create-tenant) but follows the guidance in this article, the App ID URI is managed differently by ME-ID. For more information, see the [Use of an Azure Active Directory B2C tenant](#use-of-an-azure-active-directory-b2c-tenant) section of this article. For additional security scenario coverage after reading this article, see . @@ -40,11 +41,14 @@ Follow the guidance in [Quickstart: Set up a tenant](/azure/active-directory/dev Register an ME-ID app for the *Server API app*: -1. Navigate to **Microsoft Entra ID** in the Azure portal. Select **App registrations** in the sidebar. Select the **New registration** button. + + +1. Navigate to [**Microsoft Entra ID** in the Azure portal](https://entra.microsoft.com/#home). Select **Applications** > **App registrations** in the sidebar. Select the **New registration** button. 1. Provide a **Name** for the app (for example, **Blazor Server ME-ID**). 1. Choose a **Supported account types**. You may select **Accounts in this organizational directory only** (single tenant) for this experience. 1. The *Server API app* doesn't require a **Redirect URI** in this scenario, so leave the **Select a platform** dropdown list unselected and don't enter a redirect URI. -1. This article assumes the app is registered in an **Microsoft Entra ID** tenant. If the app is registered in an **Azure Active Directory B2C** tenant, the **Permissions** > **Grant admin consent to openid and offline_access permissions** checkbox is present and selected. Deselect the checkbox to disable the setting. When using an **Active Azure Directory** tenant, the checkbox isn't present. +1. This article assumes the app is registered in an **Azure Active Directory** tenant. If the app is registered in an **Azure Active Directory B2C** tenant, the **Permissions** > **Grant admin consent to openid and offline_access permissions** checkbox is present and selected. Deselect the checkbox to disable the setting. When using an **Active Azure Directory** tenant, the checkbox isn't present. 1. Select **Register**. Record the following information: @@ -78,11 +82,14 @@ Record the following information: Register an ME-ID app for the *Client app*: + + 1. Navigate to **Microsoft Entra ID** in the Azure portal. Select **App registrations** in the sidebar. Select the **New registration** button. 1. Provide a **Name** for the app (for example, **Blazor Client ME-ID**). 1. Choose a **Supported account types**. You may select **Accounts in this organizational directory only** (single tenant) for this experience. 1. Set the **Redirect URI** dropdown list to **Single-page application (SPA)** and provide the following redirect URI: `https://localhost/authentication/login-callback`. If you know the production redirect URI for the Azure default host (for example, `azurewebsites.net`) or the custom domain host (for example, `contoso.com`), you can also add the production redirect URI at the same time that you're providing the `localhost` redirect URI. Be sure to include the port number for non-`:443` ports in any production redirect URIs that you add. -1. This article assumes the app is registered in an **Microsoft Entra ID** tenant. If the app is registered in an **Azure Active Directory B2C** tenant, the **Permissions** > **Grant admin consent to openid and offline_access permissions** checkbox is present and selected. Deselect the checkbox to disable the setting. When using an **Active Azure Directory** tenant, the checkbox isn't present. +1. This article assumes the app is registered in an **Azure Active Directory** tenant. If the app is registered in an **Azure Active Directory B2C** tenant, the **Permissions** > **Grant admin consent to openid and offline_access permissions** checkbox is present and selected. Deselect the checkbox to disable the setting. When using an **Active Azure Directory** tenant, the checkbox isn't present. 1. Select **Register**. > [!NOTE] @@ -147,7 +154,7 @@ By default, the **:::no-loc text="Server":::** app API populates `User.Identity. To configure the app to receive the value from the `name` claim type: -* Add a namespace for to `Program.cs`: +* Add a namespace for to the `Program` file: ```csharp using Microsoft.AspNetCore.Authentication.JwtBearer; @@ -307,7 +314,7 @@ The [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages Support for instances is added that include access tokens when making requests to the **:::no-loc text="Server":::** app. -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddHttpClient("{PROJECT NAME}.ServerAPI", client => @@ -322,7 +329,7 @@ The placeholder `{PROJECT NAME}` is the project name at solution creation. For e Support for authenticating users is registered in the service container with the extension method provided by the [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages/Microsoft.Authentication.WebAssembly.Msal) package. This method sets up the services required for the app to interact with the Identity Provider (IP). -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddMsalAuthentication(options => @@ -343,7 +350,7 @@ The default access token scopes represent the list of access token scopes that a * Included by default in the sign in request. * Used to provision an access token immediately after authentication. -Additional scopes can be added as needed in `Program.cs`: +Additional scopes can be added as needed in the `Program` file: ```csharp builder.Services.AddMsalAuthentication(options => @@ -439,7 +446,7 @@ Instead of the App ID URI matching the format `api://{SERVER API APP CLIENT ID O "Audience": "https://contoso.onmicrosoft.com/41451fa7-82d9-4673-8fa5-69eff5a761fd" ``` -* In `Program.cs` of the **`Client`** app, set the audience of the scope (App ID URI) to match the server API app's audience: +* In the `Program` file of the **`Client`** app, set the audience of the scope (App ID URI) to match the server API app's audience: ```csharp options.ProviderOptions.DefaultAccessTokenScopes @@ -466,7 +473,7 @@ If the App ID URI is a custom value, you must manually update the default access Example App ID URI of `urn://custom-app-id-uri` and a scope name of `API.Access`: -* In `Program.cs` of the **:::no-loc text="Client":::** app: +* In the `Program` file of the **:::no-loc text="Client":::** app: ```csharp options.ProviderOptions.DefaultAccessTokenScopes.Add( diff --git a/aspnetcore/blazor/security/webassembly/index.md b/aspnetcore/blazor/security/webassembly/index.md index dd440314cd5c..8af1f86ef21d 100644 --- a/aspnetcore/blazor/security/webassembly/index.md +++ b/aspnetcore/blazor/security/webassembly/index.md @@ -30,6 +30,20 @@ The authentication support in Blazor WebAssembly is built on top of the OIDC Cli Other options for authenticating SPAs exist, such as the use of SameSite cookies. However, the engineering design of Blazor WebAssembly uses OAuth and OIDC as the best option for authentication in Blazor WebAssembly apps. [Token-based authentication](xref:security/anti-request-forgery#token-based-authentication) based on [JSON Web Tokens (JWTs)](https://datatracker.ietf.org/doc/html/rfc7519) was chosen over [cookie-based authentication](xref:security/anti-request-forgery#cookie-based-authentication) for functional and security reasons: +:::moniker range=">= aspnetcore-8.0" + +* Using a token-based protocol offers a smaller attack surface area, as the tokens aren't sent in all requests. +* Server endpoints don't require protection against [Cross-Site Request Forgery (CSRF)](xref:security/anti-request-forgery) because the tokens are sent explicitly. This allows you to host Blazor WebAssembly apps alongside MVC or Razor pages apps. +* Tokens have narrower permissions than cookies. For example, tokens can't be used to manage the user account or change a user's password unless such functionality is explicitly implemented. +* Tokens have a short lifetime, one hour by default, which limits the attack window. Tokens can also be revoked at any time. +* Self-contained JWTs offer guarantees to the client and server about the authentication process. For example, a client has the means to detect and validate that the tokens it receives are legitimate and were emitted as part of a given authentication process. If a third party attempts to switch a token in the middle of the authentication process, the client can detect the switched token and avoid using it. +* Tokens with OAuth and OIDC don't rely on the user agent behaving correctly to ensure that the app is secure. +* Token-based protocols, such as OAuth and OIDC, allow for authenticating and authorizing users in standalone Blazor Webassembly apps with the same set of security characteristics. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + * Using a token-based protocol offers a smaller attack surface area, as the tokens aren't sent in all requests. * Server endpoints don't require protection against [Cross-Site Request Forgery (CSRF)](xref:security/anti-request-forgery) because the tokens are sent explicitly. This allows you to host Blazor WebAssembly apps alongside MVC or Razor pages apps. * Tokens have narrower permissions than cookies. For example, tokens can't be used to manage the user account or change a user's password unless such functionality is explicitly implemented. @@ -38,6 +52,8 @@ Other options for authenticating SPAs exist, such as the use of SameSite cookies * Tokens with OAuth and OIDC don't rely on the user agent behaving correctly to ensure that the app is secure. * Token-based protocols, such as OAuth and OIDC, allow for authenticating and authorizing users of hosted Blazor WebAssembly solution clients and standalone Blazor Webassembly apps with the same set of security characteristics. +:::moniker-end + > [!IMPORTANT] > For versions of ASP.NET Core that adopt Duende Identity Server in Blazor project templates, [Duende Software](https://duendesoftware.com/) might require you to pay a license fee for production use of Duende Identity Server. For more information, see . @@ -55,7 +71,7 @@ The [`Microsoft.AspNetCore.Components.WebAssembly.Authentication`](https://www.n ## `Authentication` component -The `Authentication` component (`Pages/Authentication.razor`) handles remote authentication operations and permits the app to: +The `Authentication` component (`Authentication.razor`) handles remote authentication operations and permits the app to: * Configure app routes for authentication states. * Set UI content for authentication states. @@ -123,14 +139,14 @@ Apply the [`[Authorize]` attribute](xref:blazor/security/index#authorize-attribu Allow anonymous access to the `Authentication` component to permit redirection to the identity provider. Add the following Razor code to the `Authentication` component under its [`@page`](xref:mvc/views/razor#page) directive. - `Pages/Authentication.razor`: + `Authentication.razor`: ```razor @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @attribute [AllowAnonymous] ``` -* Add the attribute to each Razor component in the `Pages` folder under their [`@page`](xref:mvc/views/razor#page) directives: +* Add the attribute to each Razor component under the [`@page`](xref:mvc/views/razor#page) directive: ```razor @using Microsoft.AspNetCore.Authorization @@ -142,6 +158,16 @@ Apply the [`[Authorize]` attribute](xref:blazor/security/index#authorize-attribu ## Use one identity provider app registration per app +:::moniker range=">= aspnetcore-8.0" + +Some of the articles under this *Overview* pertain to Blazor hosting scenarios that involve two or more apps. A standalone Blazor WebAssembly app uses web API with authenticated users to access server resources and data provided by a server app. + +When this scenario is implemented in documentation examples, ***two*** identity provider registrations are used, one for the client app and one for the server app. Using separate registrations, for example in Microsoft Entra ID, isn't strictly required. However, using two registrations is a security best practice because it isolates the registrations by app. Using separate registrations also allows independent configuration of the client and server registrations. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + Some of the articles under this *Overview* pertain to either of the following Blazor hosting scenarios that involve two or more apps: * A hosted Blazor WebAssembly solution, which is composed of two apps: a client-side Blazor WebAssembly app and a server-side ASP.NET Core host app. Authenticated users to the client app access server resources and data provided by the server app. @@ -149,6 +175,8 @@ Some of the articles under this *Overview* pertain to either of the following Bl When these scenarios are implemented in documentation examples, ***two*** identity provider registrations are used, one for the client app and one for the server app. Using separate registrations, for example in Microsoft Entra ID, isn't strictly required. However, using two registrations is a security best practice because it isolates the registrations by app. Using separate registrations also allows independent configuration of the client and server registrations. +:::moniker-end + ## Refresh tokens Although refresh tokens can't be secured in Blazor WebAssembly apps, they can be used if you implement them with appropriate security strategies. @@ -160,8 +188,12 @@ For standalone Blazor WebAssembly apps in ASP.NET Core 6.0 or later, we recommen * A [rotated](https://auth0.com/docs/secure/tokens/refresh-tokens/refresh-token-rotation) refresh token. * A refresh token with an expiration after which a new interactive authorization flow is required to refresh the user's credentials. +:::moniker range="< aspnetcore-8.0" + For hosted Blazor WebAssembly solutions, refresh tokens can be maintained and used by the server-side app in order to access third-party APIs. For more information, see . +:::moniker-end + For more information, see the following resources: * [Microsoft identity platform refresh tokens: Refresh token lifetime](/azure/active-directory/develop/refresh-tokens#refresh-token-lifetime) @@ -196,6 +228,13 @@ If Windows Authentication is used with Blazor Webassembly or with any other SPA For more information, see . +:::moniker range="< aspnetcore-8.0" + + + ## Secure a SignalR hub To secure a SignalR hub: @@ -226,6 +265,8 @@ To secure a SignalR hub: For more information, see . +:::moniker-end + ## Logging *This section applies to Blazor WebAssembly apps in ASP.NET Core 7.0 or later.* @@ -255,12 +296,16 @@ Standalone Blazor WebAssembly apps: * [Microsoft Entra ID (ME-ID)](xref:blazor/security/webassembly/standalone-with-microsoft-entra-id) * [Azure Active Directory (AAD) B2C](xref:blazor/security/webassembly/standalone-with-azure-active-directory-b2c) +:::moniker range="< aspnetcore-8.0" + Hosted Blazor WebAssembly apps: * [Microsoft Entra ID (ME-ID)](xref:blazor/security/webassembly/hosted-with-microsoft-entra-id) * [Azure Active Directory (AAD) B2C](xref:blazor/security/webassembly/hosted-with-azure-active-directory-b2c) * [Identity Server](xref:blazor/security/webassembly/hosted-with-identity-server) +:::moniker-end + Further configuration guidance is found in the following articles: * diff --git a/aspnetcore/blazor/security/webassembly/microsoft-entra-id-groups-and-roles.md b/aspnetcore/blazor/security/webassembly/microsoft-entra-id-groups-and-roles.md index 76576574e736..fed2080b23de 100644 --- a/aspnetcore/blazor/security/webassembly/microsoft-entra-id-groups-and-roles.md +++ b/aspnetcore/blazor/security/webassembly/microsoft-entra-id-groups-and-roles.md @@ -32,9 +32,20 @@ The guidance in this article applies to the Blazor WebAssembly ME-ID deployment The article's guidance provides instructions for client and server apps: +:::moniker range=">= aspnetcore-8.0" + +* **CLIENT**: Standalone Blazor WebAssembly apps. +* **SERVER**: ASP.NET Core server API/web API apps. You can ignore the **SERVER** guidance throughout the article for a standalone Blazor WebAssembly app. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + * **CLIENT**: Standalone Blazor WebAssembly apps or the **:::no-loc text="Client":::** app of a hosted Blazor [solution](xref:blazor/tooling#visual-studio-solution-file-sln). * **SERVER**: ASP.NET Core server API/web API apps or the **:::no-loc text="Server":::** app of a hosted Blazor solution. You can ignore the **SERVER** guidance throughout the article for a standalone Blazor WebAssembly app. +:::moniker-end + The examples in this article take advantage of recent .NET features released with ASP.NET Core 6.0 or later. When using the examples in ASP.NET Core 5.0, minor modifications are required. However, the text and code examples that pertain to interacting with ME-ID and Microsoft Graph are the same for all versions of ASP.NET Core. ## Prerequisite @@ -209,7 +220,7 @@ The preceding code ignores group membership claims (`groups`) that are ME-ID Adm In the **CLIENT** app, configure the MSAL authentication to use the custom user account factory. -Confirm that the `Program.cs` file uses the namespace: +Confirm that the the `Program` file file uses the namespace: ```csharp using Microsoft.AspNetCore.Components.WebAssembly.Authentication; @@ -254,7 +265,7 @@ builder.Services.AddGraphClient(baseUrl, scopes); ## Authorization configuration -In the **CLIENT** app, create a [policy](xref:security/authorization/policies) for each [App Role](#app-roles), ME-ID Administrator Role, or security group in `Program.cs`. The following example creates a policy for the ME-ID *Billing Administrator* role: +In the **CLIENT** app, create a [policy](xref:security/authorization/policies) for each [App Role](#app-roles), ME-ID Administrator Role, or security group in the `Program` file. The following example creates a policy for the ME-ID *Billing Administrator* role: ```csharp builder.Services.AddAuthorizationCore(options => @@ -300,7 +311,7 @@ If the user isn't authorized, they're redirected to the ME-ID sign-in page. A policy check can also be [performed in code with procedural logic](xref:blazor/security/index#procedural-logic). -`Pages/CheckPolicy.razor`: +`CheckPolicy.razor`: ```razor @page "/checkpolicy" @@ -340,7 +351,7 @@ A policy check can also be [performed in code with procedural logic](xref:blazor ## Authorize server API/web API access -A **SERVER** API app can authorize users to access secure API endpoints with [authorization policies](xref:security/authorization/policies) for security groups, ME-ID Administrator Roles, and App Roles when an access token contains `groups`, `wids`, and `role` claims. The following example creates a policy for the ME-ID *Billing Administrator* role in `Program.cs` using the `wids` (well-known IDs/Role Template IDs) claims: +A **SERVER** API app can authorize users to access secure API endpoints with [authorization policies](xref:security/authorization/policies) for security groups, ME-ID Administrator Roles, and App Roles when an access token contains `groups`, `wids`, and `role` claims. The following example creates a policy for the ME-ID *Billing Administrator* role in the `Program` file using the `wids` (well-known IDs/Role Template IDs) claims: ```csharp builder.Services.AddAuthorization(options => @@ -380,9 +391,20 @@ The following example assumes that the **CLIENT** and **SERVER** apps are config * `Admin` * `Developer` +:::moniker range=">= aspnetcore-8.0" + +> [!NOTE] +> When developing a client-server pair of standalone apps (a standalone Blazor WebAssembly app and an ASP.NET Core server API/web API app), the `appRoles` manifest property of both the client and the server Azure portal app registrations must include the same configured roles. After establishing the roles in the client app's manifest, copy them in their entirety to the server app's manifest. If you don't mirror the manifest `appRoles` between the client and server app registrations, role claims aren't established for authenticated users of the server API/web API, even if their access token has the correct entries in the `role` claims. + +:::moniker-end + +:::moniker range="< aspnetcore-8.0" + > [!NOTE] > When developing a hosted Blazor WebAssembly app or a client-server pair of standalone apps (a standalone Blazor WebAssembly app and an ASP.NET Core server API/web API app), the `appRoles` manifest property of both the client and the server Azure portal app registrations must include the same configured roles. After establishing the roles in the client app's manifest, copy them in their entirety to the server app's manifest. If you don't mirror the manifest `appRoles` between the client and server app registrations, role claims aren't established for authenticated users of the server API/web API, even if their access token has the correct entries in the `role` claims. +:::moniker-end + Although you can't assign roles to groups without an Microsoft Entra ID Premium account, you can assign roles to users and receive a `role` claim for users with a standard Azure account. The guidance in this section doesn't require an ME-ID Premium account. If you have a Premium tier Azure account, **Manage** > **App roles** appears in the Azure portal app registration sidebar. Follow the guidance in [How to: Add app roles in your application and receive them in the token](/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps) to configure the app's roles. @@ -437,7 +459,7 @@ Multiple roles are assigned in the Azure portal by ***re-adding a user*** for ea The `CustomAccountFactory` shown in the [Custom user account](#custom-user-account) section is set up to act on a `role` claim with a JSON array value. Add and register the `CustomAccountFactory` in the **CLIENT** app as shown in the [Custom user account](#custom-user-account) section. There's no need to provide code to remove the original `role` claim because it's automatically removed by the framework. -In `Program.cs` of a **CLIENT** app, specify the claim named "`appRole`" as the role claim for checks: +In the `Program` file of a **CLIENT** app, specify the claim named "`appRole`" as the role claim for checks: ```csharp builder.Services.AddMsalAuthentication(options => @@ -451,7 +473,7 @@ builder.Services.AddMsalAuthentication(options => > [!NOTE] > If you prefer to use the `directoryRoles` claim (ADD Administrator Roles), assign "`directoryRoles`" to the . -In `Program.cs` of a **SERVER** app, specify the claim named "`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`" as the role claim for checks: +In the `Program` file of a **SERVER** app, specify the claim named "`http://schemas.microsoft.com/ws/2008/06/identity/claims/role`" as the role claim for checks: ```csharp builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) diff --git a/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md b/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md index 3c4ad9c46fae..ce3e2a56c8e1 100644 --- a/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md +++ b/aspnetcore/blazor/security/webassembly/standalone-with-authentication-library.md @@ -139,7 +139,7 @@ The @@ -167,7 +167,7 @@ The Blazor WebAssembly template automatically configures default scopes for `ope The Blazor WebAssembly template doesn't automatically configure the app to request an access token for a secure API. To provision an access token as part of the sign-in flow, add the scope to the default token scopes of the . If adding authentication to an app, manually add the following code and configure the scope URI. -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddOidcAuthentication(options => diff --git a/aspnetcore/blazor/security/webassembly/standalone-with-azure-active-directory-b2c.md b/aspnetcore/blazor/security/webassembly/standalone-with-azure-active-directory-b2c.md index 9163ae753eac..db34198bb499 100644 --- a/aspnetcore/blazor/security/webassembly/standalone-with-azure-active-directory-b2c.md +++ b/aspnetcore/blazor/security/webassembly/standalone-with-azure-active-directory-b2c.md @@ -121,7 +121,7 @@ The [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages Support for authenticating users is registered in the service container with the extension method provided by the [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages/Microsoft.Authentication.WebAssembly.Msal) package. This method sets up all of the services required for the app to interact with the Identity Provider (IP). -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddMsalAuthentication(options => diff --git a/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-accounts.md b/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-accounts.md index 04aad11655b3..e74758de9a95 100644 --- a/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-accounts.md +++ b/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-accounts.md @@ -97,7 +97,7 @@ The [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages Support for authenticating users is registered in the service container with the extension method provided by the [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages/Microsoft.Authentication.WebAssembly.Msal) package. This method sets up all of the services required for the app to interact with the Identity Provider (IP). -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddMsalAuthentication(options => diff --git a/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-entra-id.md b/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-entra-id.md index 34d05f6cb98f..56726065ab0c 100644 --- a/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-entra-id.md +++ b/aspnetcore/blazor/security/webassembly/standalone-with-microsoft-entra-id.md @@ -33,7 +33,7 @@ Follow the guidance in [Quickstart: Set up a tenant](/azure/active-directory/dev Register an ME-ID app: -1. Navigate to **Microsoft Entra ID** in the Azure portal. Select **App registrations** in the sidebar. Select the **New registration** button. +1. Navigate to [**Microsoft Entra ID** in the Azure portal](https://entra.microsoft.com/#home). Select **Applications** > **App registrations** in the sidebar. Select the **New registration** button. 1. Provide a **Name** for the app (for example, **Blazor Standalone ME-ID**). 1. Choose a **Supported account types**. You may select **Accounts in this organizational directory only** for this experience. 1. Set the **Redirect URI** dropdown list to **Single-page application (SPA)** and provide the following redirect URI: `https://localhost/authentication/login-callback`. If you know the production redirect URI for the Azure default host (for example, `azurewebsites.net`) or the custom domain host (for example, `contoso.com`), you can also add the production redirect URI at the same time that you're providing the `localhost` redirect URI. Be sure to include the port number for non-`:443` ports in any production redirect URIs that you add. @@ -101,7 +101,7 @@ The [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages Support for authenticating users is registered in the service container with the extension method provided by the [`Microsoft.Authentication.WebAssembly.Msal`](https://www.nuget.org/packages/Microsoft.Authentication.WebAssembly.Msal) package. This method sets up the services required for the app to interact with the Identity Provider (IP). -`Program.cs`: +In the `Program` file: ```csharp builder.Services.AddMsalAuthentication(options => diff --git a/aspnetcore/security/authentication/scaffold-identity.md b/aspnetcore/security/authentication/scaffold-identity.md index bebcad909b39..43328d9a2bd5 100644 --- a/aspnetcore/security/authentication/scaffold-identity.md +++ b/aspnetcore/security/authentication/scaffold-identity.md @@ -145,7 +145,7 @@ dotnet aspnet-codegenerator identity -dc MvcAuth.Data.ApplicationDbContext --fi [!INCLUDE[](~/includes/scaffold-identity/id-scaffold-dlg-auth.md)] -## Scaffold Identity into a Blazor Server project with authorization +## Scaffold Identity into a server-side Blazor app with authorization [!INCLUDE[](~/includes/scaffold-identity/install-pkg.md)] @@ -157,7 +157,7 @@ dotnet aspnet-codegenerator identity -dc MvcAuth.Data.ApplicationDbContext --fi ### Style authentication endpoints -Because Blazor Server uses Razor Pages Identity pages, the styling of the UI changes when a visitor navigates between Identity pages and components. You have two options to address the incongruous styles: +Because server-side Blazor apps use Razor Pages Identity pages, the styling of the UI changes when a visitor navigates between Identity pages and components. You have two options to address the incongruous styles: * [Custom Identity components](#custom-identity-components) * [Use a custom layout with Blazor app styles](#use-a-custom-layout-with-blazor-app-styles) @@ -185,9 +185,9 @@ For additional assistance when seeking to build custom Identity Razor components The Identity pages layout and styles can be modified to produce pages that use styles similar to the default Blazor theme. This approach isn't covered by the documentation. -## Standalone or hosted Blazor WebAssembly apps +## Client-side Blazor apps -Client-side Blazor WebAssembly apps use their own Identity UI approaches and can't use ASP.NET Core Identity scaffolding. Server-side ASP.NET Core apps of hosted Blazor solutions can follow the Razor Pages/MVC guidance in this article and are configured just like any other type of ASP.NET Core app that supports Identity. +Client-side Blazor apps use their own Identity UI approaches and can't use ASP.NET Core Identity scaffolding. Server-side ASP.NET Core apps of hosted Blazor solutions can follow the Razor Pages/MVC guidance in this article and are configured just like any other type of ASP.NET Core app that supports Identity. The Blazor framework doesn't include Razor component versions of Identity UI pages. Identity UI Razor components can be custom built or obtained from unsupported third-party sources. @@ -443,7 +443,7 @@ dotnet aspnet-codegenerator identity -dc MvcAuth.Data.ApplicationDbContext --fi [!INCLUDE[](~/includes/scaffold-identity/id-scaffold-dlg-auth.md)] -## Scaffold Identity into a Blazor Server project without existing authorization +## Scaffold Identity into a server-side Blazor app without existing authorization [!INCLUDE[](~/includes/scaffold-identity/id-scaffold-dlg.md)] @@ -455,28 +455,28 @@ Identity is configured in `Areas/Identity/IdentityHostingStartup.cs`. For more i ### Style authentication endpoints -Because Blazor Server uses Razor Pages Identity pages, the styling of the UI changes when a visitor navigates between Identity pages and components. You have two options to address the incongruous styles: +Because server-side Blazor apps use Razor Pages Identity pages, the styling of the UI changes when a visitor navigates between Identity pages and components. You have two options to address the incongruous styles: * [Custom Identity components](#custom-identity-components) * [Use a custom layout with Blazor app styles](#use-a-custom-layout-with-blazor-app-styles) #### Custom Identity components -An approach to using components for Identity instead of pages is to build Identity components. Because `SignInManager` and `UserManager` aren't supported in Razor components, use web API endpoints in the Blazor Server app to process user account actions. +An approach to using components for Identity instead of pages is to build Identity components. Because `SignInManager` and `UserManager` aren't supported in Razor components, use web API endpoints in the Blazor app to process user account actions. #### Use a custom layout with Blazor app styles The Identity pages layout and styles can be modified to produce pages that use styles similar to the default Blazor theme. This approach isn't covered by the documentation. -## Scaffold Identity into a Blazor Server project with authorization +## Scaffold Identity into a server-side Blazor app with authorization [!INCLUDE[](~/includes/scaffold-identity/id-scaffold-dlg-auth.md)] Some Identity options are configured in `Areas/Identity/IdentityHostingStartup.cs`. For more information, see [IHostingStartup](xref:fundamentals/configuration/platform-specific-configuration). -## Standalone or hosted Blazor WebAssembly apps +## Client-side Blazor apps -Client-side Blazor WebAssembly apps use their own Identity UI approaches and can't use ASP.NET Core Identity scaffolding. Server-side ASP.NET Core apps of hosted Blazor solutions can follow the Razor Pages/MVC guidance in this article and are configured just like any other type of ASP.NET Core app that supports Identity. +Client-side Blazor apps use their own Identity UI approaches and can't use ASP.NET Core Identity scaffolding. Server-side ASP.NET Core apps of hosted Blazor solutions can follow the Razor Pages/MVC guidance in this article and are configured just like any other type of ASP.NET Core app that supports Identity. The Blazor framework doesn't include Razor component versions of Identity UI pages. Identity UI Razor components can be custom built or obtained from unsupported third-party sources. diff --git a/aspnetcore/security/authentication/social/additional-claims.md b/aspnetcore/security/authentication/social/additional-claims.md index 5d039d942246..c8cd555deaaa 100644 --- a/aspnetcore/security/authentication/social/additional-claims.md +++ b/aspnetcore/security/authentication/social/additional-claims.md @@ -91,7 +91,7 @@ The sample app saves the access token in `OnPostConfirmationAsync` (new user reg [!code-csharp[](additional-claims/samples/6.x/ClaimsSample/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs?name=snippet_OnPostConfirmationAsync&highlight=49-53,73)] > [!NOTE] -> For information on passing tokens to the Razor components of a Blazor Server app, see . +> For information on passing tokens to the Razor components of a server-side Blazor app, see . ## How to add additional custom tokens @@ -259,7 +259,7 @@ The sample app saves the access token in `OnPostConfirmationAsync` (new user reg [!code-csharp[](additional-claims/samples/3.x/ClaimsSample/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs?name=snippet_OnPostConfirmationAsync&highlight=54-56)] > [!NOTE] -> For information on passing tokens to the Razor components of a Blazor Server app, see . +> For information on passing tokens to the Razor components of a server-side Blazor app, see . ## How to add additional custom tokens