diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 6afd6bc62f18..d67fd4672f1f 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -61,16 +61,97 @@ In the app's `Program` file, call: * : Enables token acquisition to call web APIs. * `AddDownstreamApi`: Adds a named downstream web service related to a specific configuration section. -* : Adds both the app and per-user in-memory token caches. +* : Adds the .NET Core distributed token caches to the service collection. +* : Adds a default implementation of that stores cache items in memory. +* Configure the distributed token cache options (): + * In development for debugging purposes, you can disable the L1 cache by setting to `true`. ***Be sure to reset it back to `false` for production.*** + * Set the maximum size of your L1 cache with [`L1CacheOptions.SizeLimit`](xref:Microsoft.Extensions.Caching.Memory.MemoryCacheOptions.SizeLimit%2A) to prevent the cache from overrunning the server's memory. The default value is 500 MB. + * In development for debugging purposes, you can disable token encryption at rest by setting to `false`, which is the default value. ***Be sure to reset it back to `true` for production.*** + * Set token eviction from the cache with . The default value is 1 hour. + * For more information, including guidance on the callback for L2 cache failures () and asynchronous L2 cache writes (), see and [Token cache serialization: Distributed token caches](/entra/msal/dotnet/how-to/token-cache-serialization#distributed-token-caches). + +You can choose to encrypt the cache and should always do so in production. ```csharp builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")) .EnableTokenAcquisitionToCallDownstreamApi() - .AddDownstreamApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) - .AddInMemoryTokenCaches(); + .AddDownstreamApi("DownstreamApi", + builder.Configuration.GetSection("DownstreamApi")) + .AddDistributedTokenCaches(); + +// Requires the 'Microsoft.Extensions.Caching.Memory' NuGet package +builder.Services.AddDistributedMemoryCache(); + +builder.Services.Configure( + options => + { + // The following lines that are commented out reflect + // default values. We recommend overriding the default + // value of Encrypt to encrypt tokens at rest. + + //options.DisableL1Cache = false; + //options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024; + options.Encrypt = true; + //options.SlidingExpiration = TimeSpan.FromHours(1); + }); +``` + +In-memory distributed token caches are created when calling to ensure that there's a base implementation available for distributed token caching. + +Production web apps and web APIs should use a production distributed token cache (for example: [Redis](https://redis.io/), [Microsoft SQL Server](https://www.microsoft.com/sql-server), [Microsoft Azure Cosmos DB](https://azure.microsoft.com/products/cosmos-db)). + +> [!NOTE] +> For local development and testing on a single machine, you can use in-memory token caches instead of distributed token caches: +> +> ```csharp +> builder.Services.AddInMemoryTokenCaches(); +> ``` +> +> Later in the development and testing period, adopt a production distributed token cache provider. + + adds a default implementation of that stores cache items in memory, which is used by Microsoft Identity Web for token caching. + +> [!NOTE] +> requires a package reference to the [`Microsoft.Extensions.Caching.Memory` NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory). +> +> [!INCLUDE[](~/includes/package-reference.md)] + +To configure a production distributed cache provider, see . + +> [!WARNING] +> Always replace the in-memory distributed token caches with a real token cache provider when deploying the app to a production environment. If you fail to adopt a production distributed token cache provider, the app may suffer significantly degraded performance. + +For more information, see [Token cache serialization: Distributed caches](/entra/msal/dotnet/how-to/token-cache-serialization?tabs=msal#distributed-caches). However, the code examples shown don't apply to ASP.NET Core apps, which configure distributed caches via , not . + +Use a shared Data Protection key ring in production so that instances of the app across servers in a web farm can decrypt tokens when is set to `true`. + +> [!NOTE] +> For early development and local testing on a single machine, you can set to `false` and configure a shared Data Protection key ring later: +> +> ```csharp +> options.Encrypt = false; +> ``` +> +> Later in the development and testing period, enable token encryption and adopt a shared Data Protection key ring. + +The following example shows how to use [Azure Blob Storage and Azure Key Vault](xref:security/data-protection/configuration/overview#protectkeyswithazurekeyvault) for the shared key ring. Add the following packages to the server project of the Blazor Web App: + +* [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) +* [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) + +[!INCLUDE[](~/includes/package-reference.md)] + +Configure Azure Blob Storage to maintain the encrypted keys and protect them with Azure Key Vault. In the following example, the `{BLOB URI WITH SAS TOKEN}` placeholder is the full URI where the key file should be stored with the SAS token as a query string parameter, and the `{KEY IDENTIFIER}` placeholder is the key vault key identifier used for key encryption: + +```csharp +builder.Services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS TOKEN}")) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), new DefaultAzureCredential()); ``` +For more information on using a shared Data Protection key ring, see and . + Inject and call when calling on behalf of a user: ```csharp diff --git a/aspnetcore/blazor/fundamentals/signalr.md b/aspnetcore/blazor/fundamentals/signalr.md index ce55df09a16f..bb0acf4f908d 100644 --- a/aspnetcore/blazor/fundamentals/signalr.md +++ b/aspnetcore/blazor/fundamentals/signalr.md @@ -177,11 +177,11 @@ To resolve the problem, use ***either*** of the following approaches: * * [Blazor samples GitHub repository (`dotnet/blazor-samples`)](https://github.com/dotnet/blazor-samples) ([how to download](xref:blazor/fundamentals/index#sample-apps)) -## Use session affinity (sticky sessions) for server-side webfarm hosting +## Use session affinity (sticky sessions) for server-side web farm hosting When more than one backend server is in use, the app must implement session affinity, also called *sticky sessions*. Session affinity ensures that a client's circuit reconnects to the same server if the connection is dropped, which is important because client state is only held in the memory of the server that first established the client's circuit. -The following error is thrown by an app that hasn't enabled session affinity in a webfarm: +The following error is thrown by an app that hasn't enabled session affinity in a web farm: > :::no-loc text="Uncaught (in promise) Error: Invocation canceled due to the underlying connection being closed."::: diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index 7a20c11d7219..b421356cf2f1 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -169,7 +169,7 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) configOptions.BaseUrl = "{BASE ADDRESS}"; configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ]; }) - .AddInMemoryTokenCaches(); + .AddDistributedTokenCaches(); ``` Placeholders in the preceding configuration: @@ -201,7 +201,7 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) configOptions.BaseUrl = "https://localhost:7277"; configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ]; }) - .AddInMemoryTokenCaches(); + .AddDistributedTokenCaches(); ``` :::zone-end @@ -379,7 +379,7 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) configOptions.BaseUrl = "{BASE ADDRESS}"; configOptions.Scopes = [ "{APP ID URI}/Weather.Get" ]; }) - .AddInMemoryTokenCaches(); + .AddDistributedTokenCaches(); ``` Placeholders in the preceding configuration: @@ -411,11 +411,14 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) configOptions.BaseUrl = "https://localhost:7277"; configOptions.Scopes = [ "api://11112222-bbbb-3333-cccc-4444dddd5555/Weather.Get" ]; }) - .AddInMemoryTokenCaches(); + .AddDistributedTokenCaches(); ``` :::zone-end +> [!WARNING] +> Production apps should use a production distributed token cache provider. Otherwise, the app may have poor performance in some scenarios. For more information, see the [Use a production distributed token cache provider](#use-a-production-distributed-token-cache-provider) section. + The callback path (`CallbackPath`) must match the redirect URI (login callback path) configured when registering the application in the Entra or Azure portal. Paths are configured in the **Authentication** blade of the app's registration. The default value of `CallbackPath` is `/signin-oidc` for a registered redirect URI of `https://localhost/signin-oidc` (a port isn't required). The is the request path within the app's base path intercepted by the OpenID Connect handler where the user agent is first returned after signing out from Entra. The sample app doesn't set a value for the path because the default value of "`/signout-callback-oidc`" is used. After intercepting the request, the OpenID Connect handler redirects to the or , if specified. @@ -640,9 +643,12 @@ builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) - configOptions.Scopes = [ "..." ]; - }) + .AddDownstreamApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi")) - .AddInMemoryTokenCaches(); + .AddDistributedTokenCaches(); ``` +> [!NOTE] +> Production apps should use a production distributed token cache provider. Otherwise, the app may have poor performance in some scenarios. For more information, see the [Use a production distributed token cache provider](#use-a-production-distributed-token-cache-provider) section. + In the `MinimalApiJwt` project, add the following app settings configuration to the `appsettings.json` file: ```json @@ -685,6 +691,88 @@ For more information on configuration, see the following resources: * * +## Use a production distributed token cache provider + +In-memory distributed token caches are created when calling to ensure that there's a base implementation available for distributed token caching. + +Production web apps and web APIs should use a production distributed token cache (for example: [Redis](https://redis.io/), [Microsoft SQL Server](https://www.microsoft.com/sql-server), [Microsoft Azure Cosmos DB](https://azure.microsoft.com/products/cosmos-db)). + +> [!NOTE] +> For local development and testing on a single machine, you can use in-memory token caches instead of distributed token caches: +> +> ```csharp +> builder.Services.AddInMemoryTokenCaches(); +> ``` +> +> Later in the development and testing period, adopt a production distributed token cache provider. + + adds a default implementation of that stores cache items in memory, which is used by Microsoft Identity Web for token caching. + +The distributed token cache is configured by : + +* In development for debugging purposes, you can disable the L1 cache by setting to `true`. ***Be sure to reset it back to `false` for production.*** +* Set the maximum size of your L1 cache with [`L1CacheOptions.SizeLimit`](xref:Microsoft.Extensions.Caching.Memory.MemoryCacheOptions.SizeLimit%2A) to prevent the cache from overrunning the server's memory. The default value is 500 MB. +* In development for debugging purposes, you can disable token encryption at rest by setting to `false`, which is the default value. ***Be sure to reset it back to `true` for production.*** +* Set token eviction from the cache with . The default value is 1 hour. +* For more information, including guidance on the callback for L2 cache failures () and asynchronous L2 cache writes (), see and [Token cache serialization: Distributed token caches](/entra/msal/dotnet/how-to/token-cache-serialization#distributed-token-caches). + +```csharp +builder.Services.AddDistributedMemoryCache(); + +builder.Services.Configure( + options => + { + // The following lines that are commented out reflect + // default values. We recommend overriding the default + // value of Encrypt to encrypt tokens at rest. + + //options.DisableL1Cache = false; + //options.L1CacheOptions.SizeLimit = 500 * 1024 * 1024; + options.Encrypt = true; + //options.SlidingExpiration = TimeSpan.FromHours(1); + }); +``` + +> [!NOTE] +> requires a package reference to the [`Microsoft.Extensions.Caching.Memory` NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Caching.Memory). +> +> [!INCLUDE[](~/includes/package-reference.md)] + +To configure a production distributed cache provider, see . + +> [!WARNING] +> Always replace the in-memory distributed token caches with a real token cache provider when deploying the app to a production environment. If you fail to adopt a production distributed token cache provider, the app may suffer significantly degraded performance. + +For more information, see [Token cache serialization: Distributed caches](/entra/msal/dotnet/how-to/token-cache-serialization?tabs=msal#distributed-caches). However, the code examples shown don't apply to ASP.NET Core apps, which configure distributed caches via , not . + +Use a shared Data Protection key ring in production so that instances of the app across servers in a web farm can decrypt tokens when is set to `true`. + +> [!NOTE] +> For early development and local testing on a single machine, you can set to `false` and configure a shared Data Protection key ring later: +> +> ```csharp +> options.Encrypt = false; +> ``` +> +> Later in the development and testing period, enable token encryption and adopt a shared Data Protection key ring. + +The following example shows how to use [Azure Blob Storage and Azure Key Vault](xref:security/data-protection/configuration/overview#protectkeyswithazurekeyvault) for the shared key ring. Add the following packages to the server project of the Blazor Web App: + +* [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) +* [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) + +[!INCLUDE[](~/includes/package-reference.md)] + +Configure Azure Blob Storage to maintain the encrypted keys and protect them with Azure Key Vault. In the following example, the `{BLOB URI WITH SAS TOKEN}` placeholder is the full URI where the key file should be stored with the SAS token as a query string parameter, and the `{KEY IDENTIFIER}` placeholder is the key vault key identifier used for key encryption: + +```csharp +builder.Services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS TOKEN}")) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), new DefaultAzureCredential()); +``` + +For more information on using a shared Data Protection key ring, see and . + ## Redirect to the home page on logout The `LogInOrOut` component (`Layout/LogInOrOut.razor`) sets a hidden field for the return URL (`ReturnUrl`) to the current URL (`currentURL`). When the user signs out of the app, the identity provider returns the user to the page from which they logged out. If the user logs out from a secure page, they're returned to the same secure page and sent back through the authentication process. This authentication flow is reasonable when users need to change accounts regularly. diff --git a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md index 9e7761611084..aa23377b97f7 100644 --- a/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md +++ b/aspnetcore/release-notes/aspnetcore-10/includes/blazor.md @@ -10,6 +10,8 @@ All of our OIDC and Entra sample solutions now include a separate web API projec The sample solutions are configured in C# code in their `Program` files. To configure the solutions from app settings files (for example, `appsettings.json`) see the ***new*** *Supply configuration with the JSON configuration provider (app settings)* section of the OIDC or Entra articles. +Our Entra samples also include new guidance on using an encrypted distributed token cache for web farm hosting scenarios. + ### QuickGrid `RowClass` parameter Apply a stylesheet class to a row of the grid based on the row item using the new `RowClass` parameter. In the following example, the `GetRowCssClass` method is called on each row to conditionally apply a stylesheet class based on the row item: