From 245058301da35525fce126ff1a5a2a7bd97db891 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 6 Jun 2025 08:24:15 -0400 Subject: [PATCH 1/8] Data Protection: Managed Identity with Blob/AKV --- aspnetcore/blazor/call-web-api.md | 81 +++---- aspnetcore/blazor/file-uploads.md | 2 +- .../blazor/host-and-deploy/server/index.md | 3 + ...ount-confirmation-and-password-recovery.md | 3 + .../security/blazor-web-app-with-entra.md | 126 ++++++----- ...ount-confirmation-and-password-recovery.md | 3 + .../host-and-deploy/azure-apps/index.md | 2 +- aspnetcore/host-and-deploy/iis/advanced.md | 2 +- .../scaling-aspnet-apps.md | 9 + aspnetcore/includes/appname6.md | 2 +- .../configuration/default-settings.md | 2 +- .../includes/default-settings8.md | 2 +- .../data-protection/configuration/overview.md | 205 ++++++++++++------ .../implementation/key-encryption-at-rest.md | 67 +++--- .../implementation/key-storage-providers.md | 150 +++++++++++-- .../security/key-vault-configuration.md | 15 ++ 16 files changed, 439 insertions(+), 235 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index e84c5428e56a..a41acca5a0dc 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -5,7 +5,7 @@ description: Learn how to call a web API from Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 06/03/2025 +ms.date: 06/06/2025 uid: blazor/call-web-api --- # Call a web API from ASP.NET Core Blazor @@ -138,7 +138,7 @@ Use a shared Data Protection key ring in production so that instances of the app > > 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 (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using their dedicated documentation sets, which are listed at the end of this section. +The following example shows how to use [Azure Blob Storage and Azure Key Vault (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using their dedicated documentation sets, which are listed at the end of this section. Add the following packages to the server project of the Blazor Web App: @@ -150,65 +150,49 @@ Add the following packages to the server project of the Blazor Web App: > [!NOTE] > Before proceeding with the following steps, confirm that the app is registered with Microsoft Entra. -Configure Azure Blob Storage to maintain Data Protection keys and encrypt them at rest with Azure Key Vault: +Configure Azure Blob Storage to maintain data protection keys. Follow the guidance in . -* Create an Azure storage account. The account name in the following example is `contoso`. +Configure Azure Key Vault to encrypt the data protection keys at rest. Follow the guidance in . -* Create a container to hold the Data Protection keys. The container name in the following example is `data-protection`. - -* Create the key file on your local machine. In the following example, the key file is named `keys.xml`. You can use a text editor to create the file. - - `keys.xml`: - - ```xml - - - - ``` - -* Upload the key file (`keys.xml`) to the container of the storage account. Use the context menu's **View/edit** command at the end of the key row in the portal to confirm that the blob contains the preceding content. - -* Use the context menu's **Generate SAS** command to obtain the blob's URI with a shared access signature (SAS). When you create the SAS, use the following permissions: `Read`, `Add`, `Create`, `Write`, `Delete`. The URI is used later where the `{BLOB URI WITH SAS}` placeholder appears. - -When establishing the key vault in the Entra or Azure portal: - -* Configure the key vault to use a **Vault access policy**. Confirm that public access on the **Networking** step is **enabled** (checked). - -* In the **Access policies** pane, create a new access policy with `Get`, `Unwrap Key`, and `Wrap Key` Key permissions. Select the registered application as the service principal. - -* When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. - -The service in the following example forwards log messages from Azure SDK for logging and requires the [`Microsoft.Extensions.Azure` NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Azure). - -[!INCLUDE[](~/includes/package-reference.md)] - -At the top of the `Program` file, provide access to the API in the namespace: +Use the following code in the `Program` file where services are registered: ```csharp -using Microsoft.Extensions.Azure; -``` +// Recommended: Azure Managed Identity approach +TokenCredential? credential; -Use the following code in the `Program` file where services are registered: +if (builder.Environment.IsProduction()) +{ + credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); +} +else +{ + // Local development and testing only + credential = new DefaultAzureCredential(); +} -```csharp -builder.Services.TryAddSingleton(); +builder.Services.AddDataProtection() + .SetApplicationName("BlazorWebAppEntra") + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); +/* Blob URI with SAS approach builder.Services.AddDataProtection() + .SetApplicationName("BlazorWebAppEntra") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) - .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), new DefaultAzureCredential()); + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); +*/ ``` -`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. The container name in the following example is `data-protection`, and the storage account name is `contoso`. The key file is named `keys.xml`. +`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). -Example: +`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. -> :::no-loc text="https://contoso.blob.core.windows.net/data-protection/keys.xml?sp={PERMISSIONS}&st={START DATETIME}&se={EXPIRATION DATETIME}&spr=https&sv={STORAGE VERSION DATE}&sr=c&sig={TOKEN}"::: +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. The key vault name is `contoso` in the following example, and an access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The example key name is `data-protection`. The version of the key (`{KEY VERSION}` placeholder) is obtained from the key in the Entra or Azure portal after it's created. +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. -Example: - -> :::no-loc text="https://contoso.vault.azure.net/keys/data-protection/{KEY VERSION}"::: +> [!NOTE] +> The preceding example uses locally (Development environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). Inject and call when calling on behalf of a user: @@ -233,6 +217,9 @@ This approach is used by the `BlazorWebAppEntra` and `BlazorWebAppEntraBff` samp For more information, see the following resources: +* +* +* [Use the Azure SDK for .NET in ASP.NET Core apps](/dotnet/azure/sdk/aspnetcore-guidance?tabs=api) * [Web API documentation | Microsoft identity platform](/entra/identity-platform/index-web-api) * [A web API that calls web APIs: Call an API: Option 2: Call a downstream web API with the helper class](/entra/identity-platform/scenario-web-api-call-api-call-api?tabs=aspnetcore#option-2-call-a-downstream-web-api-with-the-helper-class) * @@ -240,8 +227,6 @@ For more information, see the following resources: * [Non-BFF pattern (Interactive Auto)](xref:blazor/security/blazor-web-app-entra?pivots=non-bff-pattern) * [BFF pattern (Interactive Auto)](xref:blazor/security/blazor-web-app-entra?pivots=non-bff-pattern-server) * [Host ASP.NET Core in a web farm: Data Protection](xref:host-and-deploy/web-farm#data-protection) -* -* * [Azure Key Vault documentation](/azure/key-vault/general/) * [Azure Storage documentation](/azure/storage/) diff --git a/aspnetcore/blazor/file-uploads.md b/aspnetcore/blazor/file-uploads.md index 91fe1ab081a0..448b246609cf 100644 --- a/aspnetcore/blazor/file-uploads.md +++ b/aspnetcore/blazor/file-uploads.md @@ -1256,7 +1256,7 @@ Consider an approach that uses [Azure Files](https://azure.microsoft.com/service * [Azure Files REST API](/rest/api/storageservices/file-service-rest-api) * [Azure Storage Blob client library for JavaScript](/javascript/api/overview/azure/storage-blob-readme) * [Blob service REST API](/rest/api/storageservices/blob-service-rest-api) -* Authorize user uploads with a user-delegated shared-access signature (SAS) token generated by the app (server-side) for each client file upload. For example, Azure offers the following SAS features: +* Authorize user uploads with a user-delegated shared access signature (SAS) token generated by the app (server-side) for each client file upload. For example, Azure offers the following SAS features: * [Azure Storage File Share client library for JavaScript: with SAS Token](/javascript/api/overview/azure/storage-file-share-readme#with-sas-token) * [Azure Storage Blob client library for JavaScript: with SAS Token](/javascript/api/overview/azure/storage-blob-readme#with-sas-token) * Provide automatic redundancy and file share backup. diff --git a/aspnetcore/blazor/host-and-deploy/server/index.md b/aspnetcore/blazor/host-and-deploy/server/index.md index 2f4fabf526f0..e39724e52efc 100644 --- a/aspnetcore/blazor/host-and-deploy/server/index.md +++ b/aspnetcore/blazor/host-and-deploy/server/index.md @@ -151,6 +151,9 @@ For a deeper exploration of scaling server-side Blazor apps on the Azure Contain :::moniker-end +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + ## IIS When using IIS, enable: diff --git a/aspnetcore/blazor/security/account-confirmation-and-password-recovery.md b/aspnetcore/blazor/security/account-confirmation-and-password-recovery.md index 92ee6728115b..ffdb5f841fe2 100644 --- a/aspnetcore/blazor/security/account-confirmation-and-password-recovery.md +++ b/aspnetcore/blazor/security/account-confirmation-and-password-recovery.md @@ -119,6 +119,9 @@ public static class AzureHelper } ``` +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + Where services are registered in the server project's `Program` file, obtain and bind the secret with [Options configuration](xref:fundamentals/configuration/options): ```csharp diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index e6cb4e2cae80..da6241289dad 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -5,7 +5,7 @@ description: Learn how to secure a Blazor Web App with Microsoft Entra ID. monikerRange: '>= aspnetcore-9.0' ms.author: wpickett ms.custom: mvc -ms.date: 06/03/2025 +ms.date: 06/06/2025 uid: blazor/security/blazor-web-app-entra zone_pivot_groups: blazor-web-app-entra-specification --- @@ -515,6 +515,9 @@ public static class AzureHelper } ``` +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + Where services are registered in the server project's `Program` file, obtain and apply the client secret using the following code: ```csharp @@ -760,7 +763,7 @@ Use a shared Data Protection key ring in production so that instances of the app > > 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 (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using their dedicated documentation sets, which are listed at the end of this section. +The following example shows how to use [Azure Blob Storage and Azure Key Vault (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using their dedicated documentation sets, which are listed at the end of this section. Confirm the presence of the following packages in the server project of the Blazor Web App: @@ -772,83 +775,80 @@ Confirm the presence of the following packages in the server project of the Blaz > [!NOTE] > Before proceeding with the following steps, confirm that the app is registered with Microsoft Entra. -The following code is typically implemented at the same time that a [production distributed token cache provider](xref:performance/caching/distributed) is implemented. Other options, both within Azure and outside of Azure, are available for managing Data Protection keys across multiple app instances, but the sample app demonstrates how to use Azure services. - -Configure Azure Blob Storage to maintain Data Protection keys and encrypt them at rest with Azure Key Vault: - -* Create an Azure storage account. The account name in the following example is `contoso`. - -* Create a container to hold the Data Protection keys. The container name in the following example is `data-protection`. - -* Create the key file on your local machine. In the following example, the key file is named `keys.xml`. You can use a text editor to create the file. - - `keys.xml`: - - ```xml - - - - ``` - -* Upload the key file (`keys.xml`) to the container of the storage account. Use the context menu's **View/edit** command at the end of the key row in the portal to confirm that the blob contains the preceding content. - -* Use the context menu's **Generate SAS** command to obtain the blob's URI with a shared access signature (SAS). When you create the SAS, use the following permissions: `Read`, `Add`, `Create`, `Write`, `Delete`. The URI is used later where the `{BLOB URI WITH SAS}` placeholder appears. +The following code is typically implemented at the same time that a [production distributed token cache provider](xref:performance/caching/distributed) is implemented. Other options, both within Azure and outside of Azure, are available for managing data protection keys across multiple app instances, but the sample app demonstrates how to use Azure services. -When establishing the key vault in the Entra or Azure portal: +Configure Azure Blob Storage to maintain data protection keys. Follow the guidance in . -* Configure the key vault to use a **Vault access policy**. Confirm that public access on the **Networking** step is **enabled** (checked). +Configure Azure Key Vault to encrypt the data protection keys at rest. Follow the guidance in . -* In the **Access policies** pane, create a new access policy with `Get`, `Unwrap Key`, and `Wrap Key` Key permissions. Select the registered application as the service principal. - -* When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. - -The service in the following example forwards log messages from Azure SDK for logging and requires the [`Microsoft.Extensions.Azure` NuGet package](https://www.nuget.org/packages/Microsoft.Extensions.Azure). - -[!INCLUDE[](~/includes/package-reference.md)] - -At the top of the `Program` file, provide access to the API in the namespace: +Use the following code in the `Program` file where services are registered: ```csharp -using Microsoft.Extensions.Azure; -``` +// Recommended: Azure Managed Identity approach +TokenCredential? credential; -Use the following code in the `Program` file where services are registered: +if (builder.Environment.IsProduction()) +{ + credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); +} +else +{ + // Local development and testing only + credential = new DefaultAzureCredential(); +} -```csharp -builder.Services.TryAddSingleton(); +builder.Services.AddDataProtection() + .SetApplicationName("BlazorWebAppEntra") + .PersistKeysToAzureBlobStorage( + new Uri("{BLOB URI}"), + credential) + .ProtectKeysWithAzureKeyVault( + new Uri("{KEY IDENTIFIER}"), + credential); +/* Blob URI with SAS approach builder.Services.AddDataProtection() + .SetApplicationName("BlazorWebAppEntra") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) - .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), new DefaultAzureCredential()); + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); +*/ ``` -`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. The container name in the following example is `data-protection`, and the storage account name is `contoso`. The key file is named `keys.xml`. +`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). -Example: +`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. -> :::no-loc text="https://contoso.blob.core.windows.net/data-protection/keys.xml?sp={PERMISSIONS}&st={START DATETIME}&se={EXPIRATION DATETIME}&spr=https&sv={STORAGE VERSION DATE}&sr=c&sig={TOKEN}"::: +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. The key vault name is `contoso` in the following example, and an access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The example key name is `data-protection`. The version of the key (`{KEY VERSION}` placeholder) is obtained from the key in the Entra or Azure portal after it's created. - -Example: +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. -> :::no-loc text="https://contoso.vault.azure.net/keys/data-protection/{KEY VERSION}"::: +> [!NOTE] +> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). Alternatively, you can configure the app to supply the values from app settings files using the JSON Configuration Provider. Add the following to the app settings file: ```json "DistributedTokenCache": { - "DisableL1Cache": false, - "L1CacheSizeLimit": 524288000, - "Encrypt": true, - "SlidingExpirationInHours": 1 - }, + "DisableL1Cache": false, + "L1CacheSizeLimit": 524288000, + "Encrypt": true, + "SlidingExpirationInHours": 1 +}, "DataProtection": { - "BlobUriWithSasToken": "{BLOB URI WITH SAS}", + "BlobUri": "{BLOB URI}", "KeyIdentifier": "{KEY IDENTIFIER}" } ``` +Example `DataProtection` section: + +```json +"DataProtection": { + "BlobUri": "https://contoso.blob.core.windows.net/data-protection/keys.xml", + "KeyIdentifier": "https://contoso.vault.azure.net/keys/data-protection/11112222bbbb3333cccc4444dddd5555" +} +``` + Make the following changes in the `Program` file: ```diff @@ -872,8 +872,13 @@ builder.Services.Configure( }); - builder.Services.AddDataProtection() -- .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) -- .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), new DefaultAzureCredential()); +- .SetApplicationName("BlazorWebAppEntra") +- .PersistKeysToAzureBlobStorage( +- new Uri("{BLOB URI}"), +- credential) +- .ProtectKeysWithAzureKeyVault( +- new Uri("{KEY IDENTIFIER}"), +- credential); ``` Add the following code where services are configured in the `Program` file: @@ -882,17 +887,22 @@ Add the following code where services are configured in the `Program` file: var config = builder.Configuration.GetSection("DataProtection"); builder.Services.AddDataProtection() + .SetApplicationName("BlazorSample") .PersistKeysToAzureBlobStorage( - new Uri(config.GetValue("BlobUriWithSasToken") ?? - throw new Exception("Missing Blob URI"))) + new Uri(config.GetValue("BlobUri") ?? + throw new Exception("Missing Blob URI")), + credential) .ProtectKeysWithAzureKeyVault( new Uri(config.GetValue("KeyIdentifier") ?? throw new Exception("Missing Key Identifier")), - new DefaultAzureCredential()); + credential); ``` For more information on using a shared Data Protection key ring and key storage providers, see the following resources: +* +* +* [Use the Azure SDK for .NET in ASP.NET Core apps](/dotnet/azure/sdk/aspnetcore-guidance?tabs=api) * [Host ASP.NET Core in a web farm: Data Protection](xref:host-and-deploy/web-farm#data-protection) * * diff --git a/aspnetcore/blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery.md b/aspnetcore/blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery.md index 7117e294fb53..5cf1618860d6 100644 --- a/aspnetcore/blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery.md +++ b/aspnetcore/blazor/security/webassembly/standalone-with-identity/account-confirmation-and-password-recovery.md @@ -130,6 +130,9 @@ public static class AzureHelper } ``` +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + Where services are registered in the server project's `Program` file, obtain and bind the secret with [Options configuration](xref:fundamentals/configuration/options): ```csharp diff --git a/aspnetcore/host-and-deploy/azure-apps/index.md b/aspnetcore/host-and-deploy/azure-apps/index.md index ff10bc5fca70..6138bbeaf417 100644 --- a/aspnetcore/host-and-deploy/azure-apps/index.md +++ b/aspnetcore/host-and-deploy/azure-apps/index.md @@ -130,7 +130,7 @@ See the common deployment configuration errors for apps hosted by Azure App Serv ## Data Protection key ring and deployment slots -[Data Protection keys](xref:security/data-protection/implementation/key-management#data-protection-implementation-key-management) are persisted to the *%HOME%\ASP.NET\DataProtection-Keys* folder. This folder is backed by network storage and is synchronized across all machines hosting the app. Keys aren't protected at rest. This folder supplies the key ring to all instances of an app in a single deployment slot. Separate deployment slots, such as Staging and Production, don't share a key ring. +[ASP.NET Core Data Protection keys](xref:security/data-protection/implementation/key-management#data-protection-implementation-key-management) are persisted to the *%HOME%\ASP.NET\DataProtection-Keys* folder. This folder is backed by network storage and is synchronized across all machines hosting the app. Keys aren't protected at rest. This folder supplies the key ring to all instances of an app in a single deployment slot. Separate deployment slots, such as Staging and Production, don't share a key ring. When swapping between deployment slots, any system using data protection won't be able to decrypt stored data using the key ring inside the previous slot. ASP.NET Cookie Middleware uses data protection to protect its cookies. This leads to users being signed out of an app that uses the standard ASP.NET Cookie Middleware. For a slot-independent key ring solution, use an external key ring provider, such as: diff --git a/aspnetcore/host-and-deploy/iis/advanced.md b/aspnetcore/host-and-deploy/iis/advanced.md index e2b23ab96e7f..0881be8d6c00 100644 --- a/aspnetcore/host-and-deploy/iis/advanced.md +++ b/aspnetcore/host-and-deploy/iis/advanced.md @@ -112,7 +112,7 @@ To configure data protection under IIS to persist the key ring, use **one** of t * **Create Data Protection Registry keys** - Data Protection keys used by ASP.NET Core apps are stored in the registry external to the apps. To persist the keys for a given app, create Registry keys for the app pool. + ASP.NET Core Data Protection keys used by ASP.NET Core apps are stored in the registry external to the apps. To persist the keys for a given app, create Registry keys for the app pool. For standalone, non-webfarm IIS installations, the [Data Protection Provision-AutoGenKeys.ps1 PowerShell script](https://github.com/dotnet/AspNetCore/blob/main/src/DataProtection/Provision-AutoGenKeys.ps1) can be used for each app pool used with an ASP.NET Core app. This script creates a Registry key in the HKLM registry that's accessible only to the worker process account of the app's app pool. Keys are encrypted at rest using DPAPI with a machine-wide key. diff --git a/aspnetcore/host-and-deploy/scaling-aspnet-apps/scaling-aspnet-apps.md b/aspnetcore/host-and-deploy/scaling-aspnet-apps/scaling-aspnet-apps.md index 433da47f4878..0685e74731ce 100644 --- a/aspnetcore/host-and-deploy/scaling-aspnet-apps/scaling-aspnet-apps.md +++ b/aspnetcore/host-and-deploy/scaling-aspnet-apps/scaling-aspnet-apps.md @@ -257,6 +257,9 @@ The necessary Azure resources have been created. In this section the app code is The preceding changes allow the app to manage data protection using a centralized, scalable architecture. `DefaultAzureCredential` discovers the managed identity configurations enabled earlier when the app is redeployed. +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + Update the placeholders in `AzureURIs` section of the `appsettings.json` file to include the following: 1. Replace the `` placeholder with the name of the `scalablerazorstorageXXXX` storage account. @@ -281,6 +284,9 @@ Test the app again by searching for *Microsoft* in the search field. The page sh The existing code and configuration of the app can also work while running locally during development. The `DefaultAzureCredential` class configured previously is able to pick up local environment credentials to authenticate to Azure Services. You'll need to assign the same roles to your own account that were assigned to your app's managed identity in order for the authentication to work. This should be the same account you use to log into Visual Studio or the Azure CLI. +> [!NOTE] +> simplifies authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + #### Sign-in to your local development environment You'll need to be signed in to the Azure CLI, Visual Studio, or Azure PowerShell for your credentials to be picked up by `DefaultAzureCredential`. @@ -331,4 +337,7 @@ You may need to wait again for this role assignment to propagate. You can then return to Visual Studio and run the app locally. The code should continue to function as expected. `DefaultAzureCredential` uses your existing credentials from Visual Studio or the Azure CLI. +> [!NOTE] +> simplifies authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + [!INCLUDE[](~/includes/reliableWAP_H2.md)] \ No newline at end of file diff --git a/aspnetcore/includes/appname6.md b/aspnetcore/includes/appname6.md index d9f17ae87f57..ffb211063965 100644 --- a/aspnetcore/includes/appname6.md +++ b/aspnetcore/includes/appname6.md @@ -1,3 +1,3 @@ ## Application name change - In .NET 6, normalizes the content root path to end with a . Most apps migrating from or won't have the same app name because they aren't normalized. For more information, see [SetApplicationName](xref:security/data-protection/configuration/overview#setapplicationname) \ No newline at end of file + In .NET 6, normalizes the content root path to end with a . Most apps migrating from or won't have the same app name because they aren't normalized. For more information, see [SetApplicationName](xref:security/data-protection/configuration/overview#set-the-application-name-setapplicationname) \ No newline at end of file diff --git a/aspnetcore/security/data-protection/configuration/default-settings.md b/aspnetcore/security/data-protection/configuration/default-settings.md index ddcba6c73beb..8746e7875a13 100644 --- a/aspnetcore/security/data-protection/configuration/default-settings.md +++ b/aspnetcore/security/data-protection/configuration/default-settings.md @@ -36,7 +36,7 @@ The app attempts to detect its operational environment and handle key configurat The developer is always in full control and can override how and where keys are stored. The first three options above should provide good defaults for most apps similar to how the ASP.NET **\** auto-generation routines worked in the past. The final, fallback option is the only scenario that requires the developer to specify [configuration](xref:security/data-protection/configuration/overview) upfront if they want key persistence, but this fallback only occurs in rare situations. -When hosting in a Docker container, keys should be persisted in a folder that's a Docker volume (a shared volume or a host-mounted volume that persists beyond the container's lifetime) or in an external provider, such as [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) or [Redis](https://redis.io/). An external provider is also useful in web farm scenarios if apps can't access a shared network volume (see [PersistKeysToFileSystem](xref:security/data-protection/configuration/overview#persistkeystofilesystem) for more information). +When hosting in a Docker container, keys should be persisted in a folder that's a Docker volume (a shared volume or a host-mounted volume that persists beyond the container's lifetime) or in an external provider, such as [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) or [Redis](https://redis.io/). An external provider is also useful in web farm scenarios if apps can't access a shared network volume (see [PersistKeysToFileSystem](xref:security/data-protection/configuration/overview#persist-keys-to-the-file-system-persistkeystofilesystem) for more information). > [!WARNING] > If the developer overrides the rules outlined above and points the Data Protection system at a specific key repository, automatic encryption of keys at rest is disabled. At-rest protection can be re-enabled via [configuration](xref:security/data-protection/configuration/overview). diff --git a/aspnetcore/security/data-protection/configuration/default-settings/includes/default-settings8.md b/aspnetcore/security/data-protection/configuration/default-settings/includes/default-settings8.md index 036b7d5fb00c..5de7460f267c 100644 --- a/aspnetcore/security/data-protection/configuration/default-settings/includes/default-settings8.md +++ b/aspnetcore/security/data-protection/configuration/default-settings/includes/default-settings8.md @@ -24,7 +24,7 @@ The app attempts to detect its operational environment and handle key configurat The developer is always in full control and can override how and where keys are stored. The first three options above should provide good defaults for most apps similar to how the ASP.NET **\** auto-generation routines worked in the past. The final, fallback option is the only scenario that requires the developer to specify [configuration](xref:security/data-protection/configuration/overview) upfront if they want key persistence, but this fallback only occurs in rare situations. -When hosting in a Docker container, keys should be persisted in a folder that's a Docker volume (a shared volume or a host-mounted volume that persists beyond the container's lifetime) or in an external provider, such as [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) or [Redis](https://redis.io/). An external provider is also useful in web farm scenarios if apps can't access a shared network volume (see [PersistKeysToFileSystem](xref:security/data-protection/configuration/overview#persistkeystofilesystem) for more information). +When hosting in a Docker container, keys should be persisted in a folder that's a Docker volume (a shared volume or a host-mounted volume that persists beyond the container's lifetime) or in an external provider, such as [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) or [Redis](https://redis.io/). An external provider is also useful in web farm scenarios if apps can't access a shared network volume (see [PersistKeysToFileSystem](xref:security/data-protection/configuration/overview#persist-keys-to-the-file-system-persistkeystofilesystem) for more information). > [!WARNING] > If the developer overrides the rules outlined above and points the Data Protection system at a specific key repository, automatic encryption of keys at rest is disabled. At-rest protection can be re-enabled via [configuration](xref:security/data-protection/configuration/overview). diff --git a/aspnetcore/security/data-protection/configuration/overview.md b/aspnetcore/security/data-protection/configuration/overview.md index d42b85891ff9..a1c7eba5e9eb 100644 --- a/aspnetcore/security/data-protection/configuration/overview.md +++ b/aspnetcore/security/data-protection/configuration/overview.md @@ -5,7 +5,7 @@ description: Learn how to configure Data Protection in ASP.NET Core. monikerRange: '>= aspnetcore-3.1' ms.author: tdykstra ms.custom: mvc -ms.date: 10/30/2024 +ms.date: 06/06/2025 uid: security/data-protection/configuration/overview --- # Configure ASP.NET Core Data Protection @@ -34,34 +34,72 @@ The following NuGet packages are required for the Data Protection extensions use * [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) -## ProtectKeysWithAzureKeyVault +## Protect keys with Azure Key Vault (`ProtectKeysWithAzureKeyVault`) -Sign in to Azure using the CLI, for example: +To interact with [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) locally using developer credentials, either sign into your storage account in Visual Studio or sign in to Azure using the .NET CLI: ```azurecli az login ``` -To manage keys with [Azure Key Vault](/azure/key-vault/general/overview), configure the system with in `Program.cs`. `blobUriWithSasToken` is the full URI where the key file should be stored. The URI must contain the SAS token as a query string parameter: +For more information, see [Sign-in to Azure using developer tooling](/dotnet/azure/sdk/authentication/local-development-dev-accounts#sign-in-to-azure-using-developer-tooling). -:::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionProtectKeysWithAzureKeyVault"::: +When establishing the key vault in the Entra or Azure portal: -For an app to communicate and authorize itself with KeyVault, the [Azure.Identity](https://www.nuget.org/packages/Azure.Identity/) package must be added. +* Configure the key vault to Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. -Set the key ring storage location (for example, ). The location must be set because calling `ProtectKeysWithAzureKeyVault` implements an that disables automatic data protection settings, including the key ring storage location. The preceding example uses Azure Blob Storage to persist the key ring. For more information, see [Key storage providers: Azure Storage](xref:security/data-protection/implementation/key-storage-providers#azure-storage). You can also persist the key ring locally with [PersistKeysToFileSystem](xref:security/data-protection/implementation/key-storage-providers#file-system). +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. -The `keyIdentifier` is the key vault key identifier used for key encryption. For example, a key created in key vault named `dataprotection` in the `contosokeyvault` has the key identifier `https://contosokeyvault.vault.azure.net/keys/dataprotection/`. Provide the app with **Get**, **Unwrap Key** and **Wrap Key** permissions to the key vault. +* When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. -`ProtectKeysWithAzureKeyVault` overloads: +* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure you are using a new key vault key at the time of data protection key rotation. Also, either manually change the key identifier in the app or write custom code to adopt the latest key identifier for the latest key vault key when automatic key rotation occurs (such code is currently beyond the scope of this coverage). -* permits the use of a keyIdentifier Uri and a tokenCredential to enable the data protection system to use the key vault. -* permits the use of a keyIdentifier string and IKeyEncryptionKeyResolver to enable the data protection system to use the key vault. +Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). You can also persist the key ring locally with [`PersistKeysToFileSystem`](xref:security/data-protection/implementation/key-storage-providers#file-system). -If the app uses the older Azure packages (Microsoft.AspNetCore.DataProtection.AzureStorage and Microsoft.AspNetCore.DataProtection.AzureKeyVault), we recommend ***removing*** these references and upgrading to the [Azure.Extensions.AspNetCore.DataProtection.Blobs](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [Azure.Extensions.AspNetCore.DataProtection.Keys](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys). These packages are where new updates are provided, and address some key security and stability issues with the older packages. +To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . -:::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionProtectKeysWithAzureKeyVaultConnectionString"::: +For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. -## PersistKeysToFileSystem +In the `Program` file where services are registered: + +```csharp +TokenCredential? credential; + +if (builder.Environment.IsProduction()) +{ + credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); +} +else +{ + // Local development and testing only + credential = new DefaultAzureCredential(); +} + +builder.Services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); + +/* Alternative without using an Azure Managed Identity +builder.Services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); +*/ +``` + +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. + +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. + +For an app to communicate and authorize itself with Azure Key Vault, the [`Azure.Identity` NuGet package](https://www.nuget.org/packages/Azure.Identity/) must be referenced by the app. + +[!INCLUDE[](~/includes/package-reference.md)] + +> [!NOTE] +> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + +If the app uses the older Azure packages (`Microsoft.AspNetCore.DataProtection.AzureStorage` and `Microsoft.AspNetCore.DataProtection.AzureKeyVault`), we recommend ***removing*** these references and upgrading to the [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) packages. The newer packages address key security and stability issues. + +## Persist keys to the file system (`PersistKeysToFileSystem`) To store keys on a UNC share instead of at the *%LOCALAPPDATA%* default location, configure the system with : @@ -70,7 +108,7 @@ To store keys on a UNC share instead of at the *%LOCALAPPDATA%* default location > [!WARNING] > If you change the key persistence location, the system no longer automatically encrypts keys at rest, since it doesn't know whether DPAPI is an appropriate encryption mechanism. -## PersistKeysToDbContext +## Persist keys in a database (`PersistKeysToDbContext`) To store keys in a database using EntityFramework, configure the system with the [Microsoft.AspNetCore.DataProtection.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/) package: @@ -82,7 +120,7 @@ The preceding code stores the keys in the configured database. The database cont This property represents the table in which the keys are stored. Create the table manually or with `DbContext` Migrations. For more information, see . -## ProtectKeysWith\* +## Protect keys configuration API (`ProtectKeysWith\*`) You can configure the system to protect keys at rest by calling any of the [ProtectKeysWith\*](xref:Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions) configuration APIs. Consider the example below, which stores keys on a UNC share and encrypts those keys at rest with a specific X.509 certificate: @@ -94,19 +132,19 @@ You can provide an certificates with : :::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionUnprotectKeysWithAnyCertificate"::: -## SetDefaultKeyLifetime +## Set the default key lifetime (`SetDefaultKeyLifetime`) To configure the system to use a key lifetime of 14 days instead of the default 90 days, use : :::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionSetDefaultKeyLifetime"::: -## SetApplicationName +## Set the application name (`SetApplicationName`) By default, the Data Protection system isolates apps from one another based on their [content root](xref:fundamentals/index#content-root) paths, even if they share the same physical key repository. This isolation prevents the apps from understanding each other's protected payloads. @@ -150,7 +188,7 @@ For more information on how the discriminator is used, see the following section > app.Run(); > ``` -## DisableAutomaticKeyGeneration +## Disable automatic key generation (`DisableAutomaticKeyGeneration`) You may have a scenario where you don't want an app to automatically roll keys (create new keys) as they approach expiration. One example of this scenario might be apps set up in a primary/secondary relationship, where only the primary app is responsible for key management concerns and secondary apps simply have a read-only view of the key ring. The secondary apps can be configured to treat the key ring as read-only by configuring the system with : @@ -169,7 +207,7 @@ The unique identifier is designed to survive resets—both of the individual This isolation mechanism assumes that the apps aren't malicious. A malicious app can always impact any other app running under the same worker process account. In a shared hosting environment where apps are mutually untrusted, the hosting provider should take steps to ensure OS-level isolation between apps, including separating the apps' underlying key repositories. -If the Data Protection system isn't provided by an ASP.NET Core host (for example, if you instantiate it via the `DataProtectionProvider` concrete type) app isolation is disabled by default. When app isolation is disabled, all apps backed by the same keying material can share payloads as long as they provide the appropriate [purposes](xref:security/data-protection/consumer-apis/purpose-strings). To provide app isolation in this environment, call the [`SetApplicationName`](#setapplicationname) method on the configuration object and provide a unique name for each app. +If the Data Protection system isn't provided by an ASP.NET Core host (for example, if you instantiate it via the `DataProtectionProvider` concrete type) app isolation is disabled by default. When app isolation is disabled, all apps backed by the same keying material can share payloads as long as they provide the appropriate [purposes](xref:security/data-protection/consumer-apis/purpose-strings). To provide app isolation in this environment, call the [`SetApplicationName`](#set-the-application-name-setapplicationname) method on the configuration object and provide a unique name for each app. ### Data Protection and app isolation @@ -177,7 +215,7 @@ Consider the following points for app isolation: * When multiple apps are pointed at the same key repository, the intention is that the apps share the same master key material. Data Protection is developed with the assumption that all apps sharing a key ring can access all items in that key ring. The application unique identifier is used to isolate application specific keys derived from the key ring provided keys. It doesn't expect item level permissions, such as those provided by Azure KeyVault to be used to enforce extra isolation. Attempting item level permissions generates application errors. If you don't want to rely on the built-in application isolation, separate key store locations should be used and not shared between applications. -* The application discriminator () is used to allow different apps to share the same master key material but to keep their cryptographic payloads distinct from one another. For the apps to be able to read each other's cryptographic payloads, they must have the same application discriminator, which can be set by calling [`SetApplicationName`](#setapplicationname). +* The application discriminator () is used to allow different apps to share the same master key material but to keep their cryptographic payloads distinct from one another. For the apps to be able to read each other's cryptographic payloads, they must have the same application discriminator, which can be set by calling [`SetApplicationName`](#set-the-application-name-setapplicationname). * If an app is compromised (for example, by an RCE attack), all master key material accessible to that app must also be considered compromised, regardless of its protection-at-rest state. This implies that if two apps are pointed at the same repository, even if they use different app discriminators, a compromise of one is functionally equivalent to a compromise of both. @@ -185,7 +223,7 @@ Consider the following points for app isolation: * If apps need to remain truly isolated from one another, they should use different key repositories. This naturally falls out of the definition of "isolated". Apps are ***not*** isolated if they all have Read and Write access to each other's data stores. -## Changing algorithms with UseCryptographicAlgorithms +## Changing algorithms with `UseCryptographicAlgorithms` The Data Protection stack allows you to change the default algorithm used by newly generated keys. The simplest way to do this is to call from the configuration callback: @@ -236,19 +274,19 @@ Though not exposed as a first-class API, the Data Protection system is extensibl When hosting in a [Docker](/dotnet/standard/microservices-architecture/container-docker-introduction/) container, keys should be maintained in either: * A folder that's a Docker volume that persists beyond the container's lifetime, such as a shared volume or a host-mounted volume. -* An external provider, such as [Azure Blob Storage](/azure/storage/blobs/storage-blobs-introduction) (shown in the [`ProtectKeysWithAzureKeyVault`](#protectkeyswithazurekeyvault) section) or [Redis](https://redis.io). +* An external provider, such as [Azure Blob Storage](/azure/storage/blobs/storage-blobs-introduction) (shown in the [`ProtectKeysWithAzureKeyVault`](#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) section) or [Redis](https://redis.io). ## Persisting keys with Redis Only Redis versions supporting [Redis Data Persistence](/azure/azure-cache-for-redis/cache-how-to-premium-persistence) should be used to store keys. [Azure Blob storage](/azure/storage/blobs/storage-blobs-introduction) is persistent and can be used to store keys. For more information, see [this GitHub issue](https://github.com/dotnet/AspNetCore/issues/13476). -## Logging DataProtection +## Logging -Enable `Information` level logging of DataProtection to help diagnosis problem. The following `appsettings.json` file enables information logging of the DataProtection API: +Enable the `Information` or lower level of logging to diagnose problems. The following `appsettings.json` file enables information logging of the Data Protection API: :::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/appsettings.json" highlight="6"::: -For more information on logging, see [Logging in .NET Core and ASP.NET Core](xref:fundamentals/logging/index). +For more information on logging, see . ## Additional resources @@ -269,58 +307,83 @@ When the Data Protection system is initialized, it applies [default settings](xr For these scenarios, the Data Protection system offers a rich configuration API. > [!WARNING] -> Similar to configuration files, the data protection key ring should be protected using appropriate permissions. You can choose to encrypt keys at rest, but this doesn't prevent cyberattackers from creating new keys. Consequently, your app's security is impacted. The storage location configured with Data Protection should have its access limited to the app itself, similar to the way you would protect configuration files. For example, if you choose to store your key ring on disk, use file system permissions. Ensure only the identity under which your web app runs has read, write, and create access to that directory. If you use Azure Blob Storage, only the web app should have the ability to read, write, or create new entries in the blob store, etc. +> Similar to configuration files, the data protection key ring should be protected using appropriate permissions. You can choose to encrypt keys at rest, but this doesn't prevent cyberattackers from creating new keys. Consequently, your app's security is impacted. The storage location configured with Data Protection should have its access limited to the app itself, similar to the way you would protect configuration files. For example, if you choose to store your key ring on disk, use file system permissions. Ensure only the identity under which your web app runs has read, write, and create access to that directory. If you use Azure Blob Storage, only the web app should have the ability to read, write, or create new entries in the blob store. > -> The extension method returns an . `IDataProtectionBuilder` exposes extension methods that you can chain together to configure Data Protection options. +> The extension method returns an , which exposes extension methods that you can chain together to configure Data Protection options. The following NuGet packages are required for the Data Protection extensions used in this article: -* [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) +* [`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)] -## ProtectKeysWithAzureKeyVault +## Protect keys with Azure Key Vault (`ProtectKeysWithAzureKeyVault`) -Sign in to Azure using the CLI, for example: +To interact with [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) locally using developer credentials, either sign into your storage account in Visual Studio or sign in to Azure using the .NET CLI: ```azurecli az login ``` -To store keys in [Azure Key Vault](https://azure.microsoft.com/services/key-vault/), configure the system with in the `Startup` class. `blobUriWithSasToken` is the full URI where the key file should be stored. The URI must contain the SAS token as a query string parameter: +For more information, see [Sign-in to Azure using developer tooling](/dotnet/azure/sdk/authentication/local-development-dev-accounts#sign-in-to-azure-using-developer-tooling). -```csharp -public void ConfigureServices(IServiceCollection services) -{ - services.AddDataProtection() - .PersistKeysToAzureBlobStorage(new Uri("")) - .ProtectKeysWithAzureKeyVault(new Uri(""), new DefaultAzureCredential()); -} -``` +When establishing the key vault in the Entra or Azure portal: + +* Configure the key vault to Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. -For an app to communicate and authorize itself with KeyVault, the [Azure.Identity](https://www.nuget.org/packages/Azure.Identity/) package must be added. +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. -Set the key ring storage location (for example, ). The location must be set because calling `ProtectKeysWithAzureKeyVault` implements an that disables automatic data protection settings, including the key ring storage location. The preceding example uses Azure Blob Storage to persist the key ring. For more information, see [Key storage providers: Azure Storage](xref:security/data-protection/implementation/key-storage-providers#azure-storage). You can also persist the key ring locally with [PersistKeysToFileSystem](xref:security/data-protection/implementation/key-storage-providers#file-system). +* When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. -The `keyIdentifier` is the key vault key identifier used for key encryption. For example, a key created in key vault named `dataprotection` in the `contosokeyvault` has the key identifier `https://contosokeyvault.vault.azure.net/keys/dataprotection/`. Provide the app with **Get**, **Unwrap Key** and **Wrap Key** permissions to the key vault. +* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure you are using a new key vault key at the time of data protection key rotation. Also, either manually change the key identifier in the app or write custom code to adopt the latest key identifier for the latest key vault key when automatic key rotation occurs (such code is currently beyond the scope of this coverage). -`ProtectKeysWithAzureKeyVault` overloads: +Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). You can also persist the key ring locally with [`PersistKeysToFileSystem`](xref:security/data-protection/implementation/key-storage-providers#file-system). -* permits the use of a keyIdentifier Uri and a tokenCredential to enable the data protection system to use the key vault. -* permits the use of a keyIdentifier string and IKeyEncryptionKeyResolver to enable the data protection system to use the key vault. +To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . -If the app uses the older Azure packages (Microsoft.AspNetCore.DataProtection.AzureStorage and Microsoft.AspNetCore.DataProtection.AzureKeyVault), we recommend ***removing*** these references and upgrading to the [Azure.Extensions.AspNetCore.DataProtection.Blobs](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [Azure.Extensions.AspNetCore.DataProtection.Keys](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys). These packages are where new updates are provided, and address some key security and stability issues with the older packages. +For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. + +In `Startup.ConfigureServices`: ```csharp +TokenCredential? credential; + +if (_env.IsProduction()) +{ + credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); +} +else +{ + // Local development and testing only + credential = new DefaultAzureCredential(); +} + +services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); + +/* Alternative without using an Azure Managed Identity services.AddDataProtection() - //This blob must already exist before the application is run - .PersistKeysToAzureBlobStorage("", "") - //Removing this line below for an initial run will ensure the file is created correctly - .ProtectKeysWithAzureKeyVault(new Uri(""), new DefaultAzureCredential()); + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); +*/ ``` -[!INCLUDE [managed-identities](~/includes/managed-identities-conn-strings.md)] +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. -## PersistKeysToFileSystem +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. + +For an app to communicate and authorize itself with Azure Key Vault, the [`Azure.Identity` NuGet package](https://www.nuget.org/packages/Azure.Identity/) must be referenced by the app. + +[!INCLUDE[](~/includes/package-reference.md)] + +> [!NOTE] +> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + +If the app uses the older Azure packages (`Microsoft.AspNetCore.DataProtection.AzureStorage` and `Microsoft.AspNetCore.DataProtection.AzureKeyVault`), we recommend ***removing*** these references and upgrading to the [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) packages. The newer packages address key security and stability issues. + +## Persist keys to the file system (`PersistKeysToFileSystem`) To store keys on a UNC share instead of at the *%LOCALAPPDATA%* default location, configure the system with : @@ -335,7 +398,7 @@ public void ConfigureServices(IServiceCollection services) > [!WARNING] > If you change the key persistence location, the system no longer automatically encrypts keys at rest, since it doesn't know whether DPAPI is an appropriate encryption mechanism. -## PersistKeysToDbContext +## Persist keys in a database (`PersistKeysToDbContext`) To store keys in a database using EntityFramework, configure the system with the [Microsoft.AspNetCore.DataProtection.EntityFrameworkCore](https://www.nuget.org/packages/Microsoft.AspNetCore.DataProtection.EntityFrameworkCore/) package: @@ -355,9 +418,9 @@ public DbSet DataProtectionKeys { get; set; } This property represents the table in which the keys are stored. Create the table manually or with `DbContext` Migrations. For more information, see . -## ProtectKeysWith\* +## `ProtectKeysWith\*` -You can configure the system to protect keys at rest by calling any of the [ProtectKeysWith\*](xref:Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions) configuration APIs. Consider the example below, which stores keys on a UNC share and encrypts those keys at rest with a specific X.509 certificate: +You can configure the system to protect keys at rest by calling any of the [`ProtectKeysWith\*`](xref:Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions) configuration APIs. Consider the example below, which stores keys on a UNC share and encrypts those keys at rest with a specific X.509 certificate: ```csharp public void ConfigureServices(IServiceCollection services) @@ -380,9 +443,9 @@ public void ConfigureServices(IServiceCollection services) } ``` -See [Key Encryption At Rest](xref:security/data-protection/implementation/key-encryption-at-rest) for more examples and discussion on the built-in key encryption mechanisms. +For more examples and discussion on the built-in key encryption mechanisms, see . -## UnprotectKeysWithAnyCertificate +## Unprotect keys with any certificate (`UnprotectKeysWithAnyCertificate`) You can rotate certificates and decrypt keys at rest using an array of certificates with : @@ -399,7 +462,7 @@ public void ConfigureServices(IServiceCollection services) } ``` -## SetDefaultKeyLifetime +## Set the default key lifetime (`SetDefaultKeyLifetime`) To configure the system to use a key lifetime of 14 days instead of the default 90 days, use : @@ -411,7 +474,7 @@ public void ConfigureServices(IServiceCollection services) } ``` -## SetApplicationName +## Set the application name (`SetApplicationName`) By default, the Data Protection system isolates apps from one another based on their [content root](xref:fundamentals/index#content-root) paths, even if they share the same physical key repository. This isolation prevents the apps from understanding each other's protected payloads. @@ -435,7 +498,7 @@ public void ConfigureServices(IServiceCollection services) * [Per-application isolation](#per-application-isolation) * [Data Protection and app isolation](#data-protection-and-app-isolation) -## DisableAutomaticKeyGeneration +## Disable automatic key generation (`DisableAutomaticKeyGeneration`) You may have a scenario where you don't want an app to automatically roll keys (create new keys) as they approach expiration. One example of this scenario might be apps set up in a primary/secondary relationship, where only the primary app is responsible for key management concerns and secondary apps simply have a read-only view of the key ring. The secondary apps can be configured to treat the key ring as read-only by configuring the system with : @@ -460,7 +523,7 @@ The unique identifier is designed to survive resets—both of the individual This isolation mechanism assumes that the apps aren't malicious. A malicious app can always impact any other app running under the same worker process account. In a shared hosting environment where apps are mutually untrusted, the hosting provider should take steps to ensure OS-level isolation between apps, including separating the apps' underlying key repositories. -If the Data Protection system isn't provided by an ASP.NET Core host (for example, if you instantiate it via the `DataProtectionProvider` concrete type) app isolation is disabled by default. When app isolation is disabled, all apps backed by the same keying material can share payloads as long as they provide the appropriate [purposes](xref:security/data-protection/consumer-apis/purpose-strings). To provide app isolation in this environment, call the [SetApplicationName](#setapplicationname) method on the configuration object and provide a unique name for each app. +If the Data Protection system isn't provided by an ASP.NET Core host (for example, if you instantiate it via the `DataProtectionProvider` concrete type) app isolation is disabled by default. When app isolation is disabled, all apps backed by the same keying material can share payloads as long as they provide the appropriate [purposes](xref:security/data-protection/consumer-apis/purpose-strings). To provide app isolation in this environment, call the [SetApplicationName](#set-the-application-name-setapplicationname) method on the configuration object and provide a unique name for each app. ### Data Protection and app isolation @@ -468,7 +531,7 @@ Consider the following points for app isolation: * When multiple apps are pointed at the same key repository, the intention is that the apps share the same master key material. Data Protection is developed with the assumption that all apps sharing a key ring can access all items in that key ring. The application unique identifier is used to isolate application specific keys derived from the key ring provided keys. It doesn't expect item level permissions, such as those provided by Azure KeyVault to be used to enforce extra isolation. Attempting item level permissions generates application errors. If you don't want to rely on the built-in application isolation, separate key store locations should be used and not shared between applications. -* The application discriminator () is used to allow different apps to share the same master key material but to keep their cryptographic payloads distinct from one another. For the apps to be able to read each other's cryptographic payloads, they must have the same application discriminator, which can be set by calling [`SetApplicationName`](#setapplicationname). +* The application discriminator () is used to allow different apps to share the same master key material but to keep their cryptographic payloads distinct from one another. For the apps to be able to read each other's cryptographic payloads, they must have the same application discriminator, which can be set by calling [`SetApplicationName`](#set-the-application-name-setapplicationname). * If an app is compromised (for example, by an RCE attack), all master key material accessible to that app must also be considered compromised, regardless of its protection-at-rest state. This implies that if two apps are pointed at the same repository, even if they use different app discriminators, a compromise of one is functionally equivalent to a compromise of both. @@ -476,7 +539,7 @@ Consider the following points for app isolation: * If apps need to remain truly isolated from one another, they should use different key repositories. This naturally falls out of the definition of "isolated". Apps are ***not*** isolated if they all have Read and Write access to each other's data stores. -## Changing algorithms with UseCryptographicAlgorithms +## Changing algorithms with `UseCryptographicAlgorithms` The Data Protection stack allows you to change the default algorithm used by newly generated keys. The simplest way to do this is to call from the configuration callback: @@ -577,17 +640,17 @@ Though not exposed as a first-class API, the Data Protection system is extensibl When hosting in a [Docker](/dotnet/standard/microservices-architecture/container-docker-introduction/) container, keys should be maintained in either: * A folder that's a Docker volume that persists beyond the container's lifetime, such as a shared volume or a host-mounted volume. -* An external provider, such as [Azure Blob Storage](/azure/storage/blobs/storage-blobs-introduction) (shown in the [`ProtectKeysWithAzureKeyVault`](#protectkeyswithazurekeyvault) section) or [Redis](https://redis.io). +* An external provider, such as [Azure Blob Storage](/azure/storage/blobs/storage-blobs-introduction) (shown in the [Protect keys with Azure Key Vault (`ProtectKeysWithAzureKeyVault`)](#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) section) or [Redis](https://redis.io). ## Persisting keys with Redis Only Redis versions supporting [Redis Data Persistence](/azure/azure-cache-for-redis/cache-how-to-premium-persistence) should be used to store keys. [Azure Blob storage](/azure/storage/blobs/storage-blobs-introduction) is persistent and can be used to store keys. For more information, see [this GitHub issue](https://github.com/dotnet/AspNetCore/issues/13476). -## Logging DataProtection +## Logging -Enable `Information` level logging of DataProtection to help diagnosis problem. The following `appsettings.json` file enables information logging of the DataProtection API: +Enable the `Information` or lower level of logging to diagnose problems. The following `appsettings.json` file enables information logging of the Data Protection API: -```JSON +```json { "Logging": { "LogLevel": { @@ -597,7 +660,7 @@ Enable `Information` level logging of DataProtection to help diagnosis problem. } ``` -For more information on logging, see [Logging in .NET Core and ASP.NET Core](xref:fundamentals/logging/index). +For more information on logging, see . ## Additional resources diff --git a/aspnetcore/security/data-protection/implementation/key-encryption-at-rest.md b/aspnetcore/security/data-protection/implementation/key-encryption-at-rest.md index b931092d0da6..20928373e47f 100644 --- a/aspnetcore/security/data-protection/implementation/key-encryption-at-rest.md +++ b/aspnetcore/security/data-protection/implementation/key-encryption-at-rest.md @@ -3,7 +3,7 @@ title: Key encryption at rest in Windows and Azure using ASP.NET Core author: rick-anderson description: Learn implementation details of ASP.NET Core Data Protection key encryption at rest. ms.author: riande -ms.date: 07/16/2018 +ms.date: 06/06/2025 uid: security/data-protection/implementation/key-encryption-at-rest --- # Key encryption at rest in Windows and Azure using ASP.NET Core @@ -17,18 +17,7 @@ The data protection system [employs a discovery mechanism by default](xref:secur ## Azure Key Vault -To store keys in [Azure Key Vault](https://azure.microsoft.com/services/key-vault/), configure the system with in the `Startup` class: - -```csharp -public void ConfigureServices(IServiceCollection services) -{ - services.AddDataProtection() - .PersistKeysToAzureBlobStorage(new Uri("")) - .ProtectKeysWithAzureKeyVault("", "", ""); -} -``` - -For more information, see [Configure ASP.NET Core Data Protection: ProtectKeysWithAzureKeyVault](xref:security/data-protection/configuration/overview#protectkeyswithazurekeyvault). +For more information, see . :::moniker-end @@ -39,39 +28,51 @@ For more information, see [Configure ASP.NET Core Data Protection: ProtectKeysWi When Windows DPAPI is used, key material is encrypted with [CryptProtectData](/windows/desktop/api/dpapi/nf-dpapi-cryptprotectdata) before being persisted to storage. DPAPI is an appropriate encryption mechanism for data that's never read outside of the current machine (though it's possible to back these keys up to Active Directory). To configure DPAPI key-at-rest encryption, call one of the ) extension methods: ```csharp -public void ConfigureServices(IServiceCollection services) -{ - // Only the local user account can decrypt the keys - services.AddDataProtection() - .ProtectKeysWithDpapi(); -} +// Only the local user account can decrypt the keys +services.AddDataProtection() + .ProtectKeysWithDpapi(); ``` If `ProtectKeysWithDpapi` is called with no parameters, only the current Windows user account can decipher the persisted key ring. You can optionally specify that any user account on the machine (not just the current user account) be able to decipher the key ring: ```csharp -public void ConfigureServices(IServiceCollection services) -{ - // All user accounts on the machine can decrypt the keys - services.AddDataProtection() - .ProtectKeysWithDpapi(protectToLocalMachine: true); -} +// All user accounts on the machine can decrypt the keys +services.AddDataProtection() + .ProtectKeysWithDpapi(protectToLocalMachine: true); ``` :::moniker range=">= aspnetcore-2.0" ## X.509 certificate -If the app is spread across multiple machines, it may be convenient to distribute a shared X.509 certificate across the machines and configure the hosted apps to use the certificate for encryption of keys at rest: +If the app is spread across multiple machines, it may be convenient to distribute a shared X.509 certificate (`.pfx` format) across the machines and configure the hosted apps to use the certificate for encryption of keys at rest. + +In the following example, the certificate's thumbprint is passed to : ```csharp -public void ConfigureServices(IServiceCollection services) -{ - services.AddDataProtection() - .ProtectKeysWithCertificate("3BCE558E2AD3E0E34A7743EAB5AEA2A9BD2575A0"); -} +services.AddDataProtection() + .ProtectKeysWithCertificate("{CERTIFICATE THUMBPRINT}"); +``` + +In the following example, an is passed to : + +```csharp +var cert = new X509Certificate2(...); + +services.AddDataProtection() + .ProtectKeysWithCertificate(cert); ``` +To create the certificate, use one of the following approaches or any other suitable tool or online service: + +* [`dotnet dev-certs` command](/dotnet/core/tools/dotnet-dev-certs) +* [`New-SelfSignedCertificate` PowerShell command](/powershell/module/pki/new-selfsignedcertificate) +* [Azure Key Vault](/azure/key-vault/certificates/quick-create-portal#add-a-certificate-to-key-vault) +* [MakeCert on Windows](/windows/desktop/seccrypto/makecert) +* [OpenSSL](https://www.openssl.org) + +For more information, see [Generate self-signed certificates with the .NET CLI](/dotnet/core/additional-tools/self-signed-certificates-guide). + Due to .NET Framework limitations, only certificates with CAPI private keys are supported. See the content below for possible workarounds to these limitations. :::moniker-end @@ -109,13 +110,13 @@ In this scenario, the AD domain controller is responsible for distributing the e ## Certificate-based encryption with Windows DPAPI-NG -If the app is running on Windows 8.1/Windows Server 2012 R2 or later, you can use Windows DPAPI-NG to perform certificate-based encryption. Use the rule descriptor string "CERTIFICATE=HashId:THUMBPRINT", where *THUMBPRINT* is the hex-encoded SHA1 thumbprint of the certificate: +If the app is running on Windows 8.1/Windows Server 2012 R2 or later, you can use Windows DPAPI-NG to perform certificate-based encryption. Use the rule descriptor string "CERTIFICATE=HashId:{CERTIFICATE THUMBPRINT}", where the `{CERTIFICATE THUMBPRINT}` placeholder is the hex-encoded SHA1 thumbprint of the certificate: ```csharp public void ConfigureServices(IServiceCollection services) { services.AddDataProtection() - .ProtectKeysWithDpapiNG("CERTIFICATE=HashId:3BCE558E2...B5AEA2A9BD2575A0", + .ProtectKeysWithDpapiNG("CERTIFICATE=HashId:{CERTIFICATE THUMBPRINT}", flags: DpapiNGProtectionDescriptorFlags.None); } ``` diff --git a/aspnetcore/security/data-protection/implementation/key-storage-providers.md b/aspnetcore/security/data-protection/implementation/key-storage-providers.md index d46a93cccf23..9207fd51f8c2 100644 --- a/aspnetcore/security/data-protection/implementation/key-storage-providers.md +++ b/aspnetcore/security/data-protection/implementation/key-storage-providers.md @@ -3,7 +3,7 @@ title: Key storage providers in ASP.NET Core author: rick-anderson description: Learn about key storage providers in ASP.NET Core and how to configure key storage locations. ms.author: riande -ms.date: 10/29/2024 +ms.date: 06/06/2025 uid: security/data-protection/implementation/key-storage-providers --- @@ -30,42 +30,154 @@ The `DirectoryInfo` can point to a directory on the local machine, or it can poi ## Azure Storage -The [Azure.Extensions.AspNetCore.DataProtection.Blobs](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) package allows storing data protection keys in Azure Blob Storage. Keys can be shared across several instances of a web app. Apps can share authentication cookies or CSRF protection across multiple servers. +The [`Azure.Extensions.AspNetCore.DataProtection.Blobs` NuGet package](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) provides API for storing data protection keys in Azure Blob Storage. Keys can be shared across several instances of a web app. Apps can share authentication cookies or CSRF protection across multiple servers. -To configure the Azure Blob Storage provider, call one of the overloads. +[!INCLUDE[](~/includes/package-reference.md)] + +Configure Azure Blob Storage to maintain data protection keys: + +* Create an Azure storage account. + +* Create a container to hold the data protection key file. + +* Use a text editor to create an XML key file on your local machine: + + ```xml + + + + ``` + +* Upload the key file to the container of the storage account. Use the context menu's **View/edit** command at the end of the key row in the portal to confirm that the blob contains the preceding content. + +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Storage Blob Data Contributor** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. + +To configure the Azure Blob Storage provider, call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). + +Other overloads are based on: + +* A blob URI and storage shared key credential (). +* A blob URI with a shared access signature (SAS). +* A connection string, container name, and blob name. +* A blob client (). This approach is demonstrated later in this section. + +For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. + +:::moniker range=">= aspnetcore-6.0" + +In the `Program` file where services are registered: ```csharp -public void ConfigureServices(IServiceCollection services) +TokenCredential? credential; + +if (builder.Environment.IsProduction()) { - services.AddDataProtection() - .PersistKeysToAzureBlobStorage(new Uri("")); + credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); +} +else +{ + // Local development and testing only + credential = new DefaultAzureCredential(); +} + +builder.Services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential); + +/* Alternative without using an Azure Managed Identity +builder.Services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")); +*/ +``` + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" + +In `Startup.ConfigureServices`: + +```csharp +TokenCredential? credential; + +if (_env.IsProduction()) +{ + credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); +} +else +{ + // Local development and testing only + credential = new DefaultAzureCredential(); } + +services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential); + +/* Alternative without using an Azure Managed Identity +services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")); +*/ ``` -If the web app is running as an Azure service, connection string can be used to authenticate to Azure storage by using [Azure.Storage.Blobs](xref:Azure.Storage.Blobs.BlobContainerClient). +`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). + +`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. + +Example: + +> :::no-loc text="https://contoso.blob.core.windows.net/data-protection/keys.xml"::: + +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. In the following example, the container name is `data-protection`, and the storage account name is `contoso`. The key file is named `keys.xml`. The shared access signature (SAS) query string is at the end of the URI (`{SHARED ACCESS SIGNATURE}` placeholder). + +Example: + +> :::no-loc text="https://contoso.blob.core.windows.net/data-protection/keys.xml{SHARED ACCESS SIGNATURE}"::: + +:::moniker-end + +If the web app is running as an Azure service, a connection string can be used to authenticate to Azure Storage using , as seen in the following example. [!INCLUDE [managed-identities](~/includes/managed-identities-conn-strings.md)] +The optional call to provisions the container automatically if it doesn't exist. + +The connection string (`{CONNECTION STRING}` placeholder) to the storage account can be found in the Entra or Azure portal under the "Access Keys" section or by running the following Azure CLI command: + +```azurecli +az storage account show-connection-string --name --resource-group +``` + +:::moniker range=">= aspnetcore-6.0" + +In the `Program` file where services are registered: + ```csharp -string connectionString = ""; -string containerName = "my-key-container"; +string connectionString = "{CONNECTION STRING}"; +string containerName = "{CONTAINER NAME}"; string blobName = "keys.xml"; -BlobContainerClient container = new BlobContainerClient(connectionString, containerName); - -// optional - provision the container automatically +var container = new BlobContainerClient(connectionString, containerName); await container.CreateIfNotExistsAsync(); +BlobClient blobClient = container.GetBlobClient(blobName); + +builder.Services.AddDataProtection().PersistKeysToAzureBlobStorage(blobClient); +``` + +:::moniker-end + +:::moniker range="< aspnetcore-6.0" +In `Startup.ConfigureServices`: + +```csharp +string connectionString = "{CONNECTION STRING}"; +string containerName = "{CONTAINER NAME}"; +string blobName = "keys.xml"; +var container = new BlobContainerClient(connectionString, containerName); +await container.CreateIfNotExistsAsync(); BlobClient blobClient = container.GetBlobClient(blobName); -services.AddDataProtection() - .PersistKeysToAzureBlobStorage(blobClient); +services.AddDataProtection().PersistKeysToAzureBlobStorage(blobClient); ``` -> [!NOTE] -> The connection string to your storage account can be found in the Azure Portal under the "Access Keys" section or by running the following CLI command: -> ```bash -> az storage account show-connection-string --name --resource-group -> ``` +:::moniker-end ## Redis diff --git a/aspnetcore/security/key-vault-configuration.md b/aspnetcore/security/key-vault-configuration.md index e8caccb71464..d5bb0073e695 100644 --- a/aspnetcore/security/key-vault-configuration.md +++ b/aspnetcore/security/key-vault-configuration.md @@ -175,6 +175,9 @@ The sample app creates an instance of the [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + Key Vault name example value: `contosovault` `appsettings.json`: @@ -361,6 +364,9 @@ builder.Configuration.AddAzureKeyVault( Disabled secrets cannot be retrieved from Key Vault and are never included. +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + ## Troubleshoot When the app fails to load configuration using the provider, an error message is written to the [ASP.NET Core Logging infrastructure](xref:fundamentals/logging/index). The following conditions will prevent configuration from loading: @@ -554,6 +560,9 @@ The sample app: :::code language="csharp" source="key-vault-configuration/samples/3.x/SampleApp/Program.cs" id="snippet2" highlight="12-14"::: +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + Key Vault name example value: `contosovault` `appsettings.json`: @@ -585,6 +594,9 @@ config.AddAzureKeyVault( }); ``` +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + The `AzureKeyVaultConfigurationOptions` object contains the following properties. | Property | Description | @@ -738,6 +750,9 @@ config.AddAzureKeyVault( Disabled secrets cannot be retrieved from Key Vault and are never included. +> [!NOTE] +> The preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). + ## Troubleshoot When the app fails to load configuration using the provider, an error message is written to the [ASP.NET Core Logging infrastructure](xref:fundamentals/logging/index). The following conditions will prevent configuration from loading: From 1d12089b87ac96952ebd37470f5c2671afa8dda2 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Fri, 6 Jun 2025 09:58:27 -0400 Subject: [PATCH 2/8] Updates --- .../aspnetcore-9/includes/delete_keys.md | 2 +- .../configuration/default-settings.md | 2 +- .../data-protection/configuration/overview.md | 30 ++- .../DataProtectionConfigurationSample.csproj | 16 ++ .../Program.cs | 6 + .../Snippets/Program.cs | 193 ++++++++++++++++++ .../Snippets/SampleDbContext.cs | 14 ++ .../appsettings.Development.json | 8 + .../appsettings.json | 10 + .../{9 => 9.x}/deleteKeys/ConsoleApp1.csproj | 0 .../samples/{9 => 9.x}/deleteKeys/Program.cs | 0 11 files changed, 275 insertions(+), 6 deletions(-) create mode 100644 aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/DataProtectionConfigurationSample.csproj create mode 100644 aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Program.cs create mode 100644 aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/Program.cs create mode 100644 aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/SampleDbContext.cs create mode 100644 aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.Development.json create mode 100644 aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.json rename aspnetcore/security/data-protection/configuration/samples/{9 => 9.x}/deleteKeys/ConsoleApp1.csproj (100%) rename aspnetcore/security/data-protection/configuration/samples/{9 => 9.x}/deleteKeys/Program.cs (100%) diff --git a/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md b/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md index e7e49b32b5f8..42ed4405c19d 100644 --- a/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md +++ b/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md @@ -2,4 +2,4 @@ Prior to .NET 9, data protection keys were ___not___ deletable by design, to prevent data loss. Deleting a key renders its protected data irretrievable. Given their small size, the accumulation of these keys generally posed minimal impact. However, to accommodate extremely long-running services, we have introduced the option to delete keys. Generally, only old keys should be deleted. Only delete keys when you can accept the risk of data loss in exchange for storage savings. We recommend data protection keys should ___not___ be deleted. -:::code language="csharp" source="~/security/data-protection/configuration/samples/9/deleteKeys/Program.cs" ::: +:::code language="csharp" source="~/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs" ::: diff --git a/aspnetcore/security/data-protection/configuration/default-settings.md b/aspnetcore/security/data-protection/configuration/default-settings.md index 8746e7875a13..140c0f766367 100644 --- a/aspnetcore/security/data-protection/configuration/default-settings.md +++ b/aspnetcore/security/data-protection/configuration/default-settings.md @@ -58,7 +58,7 @@ Deleting a key makes its protected data permanently inaccessible. To mitigate th We recommend not deleting data protection keys. -:::code language="csharp" source="~/security/data-protection/configuration/samples/9/deleteKeys/Program.cs" ::: +:::code language="csharp" source="~/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs" ::: ## Additional resources diff --git a/aspnetcore/security/data-protection/configuration/overview.md b/aspnetcore/security/data-protection/configuration/overview.md index a1c7eba5e9eb..1fc76d4778c0 100644 --- a/aspnetcore/security/data-protection/configuration/overview.md +++ b/aspnetcore/security/data-protection/configuration/overview.md @@ -122,15 +122,37 @@ This property represents the table in which the keys are stored. Create the tabl ## Protect keys configuration API (`ProtectKeysWith\*`) -You can configure the system to protect keys at rest by calling any of the [ProtectKeysWith\*](xref:Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions) configuration APIs. Consider the example below, which stores keys on a UNC share and encrypts those keys at rest with a specific X.509 certificate: +You can configure the system to protect keys at rest by calling any of the [`ProtectKeysWith\*`](xref:Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions) configuration APIs. Consider the example below, which stores keys on a UNC share and encrypts those keys at rest with a specific X.509 certificate. + +:::moniker-end + +:::moniker range=">= aspnetcore-9.0" + +You can provide an to from a file by calling : + +:::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionProtectKeysWithCertificateX509Certificate2"::: + +The following code example demonstrates how to load a certificate using a thumbprint: :::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionProtectKeysWithCertificate"::: +:::moniker-end + +:::moniker range=">= aspnetcore-6.0 < aspnetcore-9.0" + You can provide an to , such as a certificate loaded from a file: :::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionProtectKeysWithCertificateX509Certificate2"::: -See [Key Encryption At Rest](xref:security/data-protection/implementation/key-encryption-at-rest) for more examples and discussion on the built-in key encryption mechanisms. +The following code example demonstrates how to load a certificate using a thumbprint: + +:::code language="csharp" source="samples/6.x/DataProtectionConfigurationSample/Snippets/Program.cs" id="snippet_AddDataProtectionProtectKeysWithCertificate"::: + +:::moniker-end + +:::moniker range=">= aspnetcore-6.0" + +For examples and discussion on the built-in key encryption mechanisms, see . ## Unprotect keys with any certificate (`UnprotectKeysWithAnyCertificate`) @@ -186,7 +208,7 @@ For more information on how the discriminator is used, see the following section > app.MapGet("/", () => Assembly.GetEntryAssembly()!.GetName().Name); > > app.Run(); -> ``` +> ``` ## Disable automatic key generation (`DisableAutomaticKeyGeneration`) @@ -418,7 +440,7 @@ public DbSet DataProtectionKeys { get; set; } This property represents the table in which the keys are stored. Create the table manually or with `DbContext` Migrations. For more information, see . -## `ProtectKeysWith\*` +## Protect keys configuration API (`ProtectKeysWith\*`) You can configure the system to protect keys at rest by calling any of the [`ProtectKeysWith\*`](xref:Microsoft.AspNetCore.DataProtection.DataProtectionBuilderExtensions) configuration APIs. Consider the example below, which stores keys on a UNC share and encrypts those keys at rest with a specific X.509 certificate: diff --git a/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/DataProtectionConfigurationSample.csproj b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/DataProtectionConfigurationSample.csproj new file mode 100644 index 000000000000..2381e5bc0c84 --- /dev/null +++ b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/DataProtectionConfigurationSample.csproj @@ -0,0 +1,16 @@ + + + + net9.0 + enable + enable + + + + + + + + + + diff --git a/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Program.cs b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Program.cs new file mode 100644 index 000000000000..1760df1d28be --- /dev/null +++ b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Program.cs @@ -0,0 +1,6 @@ +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); + +app.Run(); diff --git a/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/Program.cs b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/Program.cs new file mode 100644 index 000000000000..bada07105048 --- /dev/null +++ b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/Program.cs @@ -0,0 +1,193 @@ +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Azure.Identity; +using Microsoft.AspNetCore.DataProtection; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; +using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; + +namespace DataProtectionConfigurationSample.Snippets; + +public static class Program +{ + public static void AddDataProtectionProtectKeysWithAzureKeyVault(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .PersistKeysToAzureBlobStorage(new Uri("")) + .ProtectKeysWithAzureKeyVault(new Uri(""), + new DefaultAzureCredential()); + // + } + + public static void AddDataProtectionProtectKeysWithAzureKeyVaultConnectionString(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + // This blob must already exist before the application is run + .PersistKeysToAzureBlobStorage( + "", "") + // Removing this line below for an initial run will ensure the file is + // created correctly + .ProtectKeysWithAzureKeyVault(new Uri(""), + new DefaultAzureCredential()); + // + } + + public static void AddDataProtectionPersistKeysToFileSystem(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .PersistKeysToFileSystem( + new DirectoryInfo(@"\\server\share\directory\")); + // + } + + public static void AddDataProtectionPersistKeysToDbContext(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .PersistKeysToDbContext(); + // + } + + public static void AddDataProtectionProtectKeysWithCertificate(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .PersistKeysToFileSystem( + new DirectoryInfo(builder.Configuration["CertificatePath"] ?? + throw new Exception("No path"))) + .ProtectKeysWithCertificate( + builder.Configuration["CertificateThumbprint"] ?? + throw new Exception("No thumbprint")); + // + } + + public static void AddDataProtectionProtectKeysWithCertificateX509Certificate2(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo( + builder.Configuration["CertificatePath"] ?? + throw new Exception("No path"))) + .ProtectKeysWithCertificate( + X509CertificateLoader.LoadCertificateFromFile( + builder.Configuration["CertificateFileName"] ?? + throw new Exception("No file name"))); + // + } + + public static void AddDataProtectionUnprotectKeysWithAnyCertificate(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .PersistKeysToFileSystem(new DirectoryInfo(@"\\server\share\directory\")) + .ProtectKeysWithCertificate( + X509CertificateLoader.LoadCertificateFromFile( + builder.Configuration["CertificateFileName"] ?? + throw new Exception("No file name"))) + .UnprotectKeysWithAnyCertificate( + X509CertificateLoader.LoadCertificateFromFile( + builder.Configuration["CertificateFileName1"] ?? + throw new Exception("No file name 1")), + X509CertificateLoader.LoadCertificateFromFile( + builder.Configuration["CertificateFileName2"] ?? + throw new Exception("No file name 2"))); + // + } + + public static void AddDataProtectionSetDefaultKeyLifetime(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .SetDefaultKeyLifetime(TimeSpan.FromDays(14)); + // + } + + public static void AddDataProtectionSetApplicationName(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .SetApplicationName(""); + // + } + + public static void AddDataProtectionDisableAutomaticKeyGeneration(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .DisableAutomaticKeyGeneration(); + // + } + + public static void AddDataProtectionUseCryptographicAlgorithms(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration + { + EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, + ValidationAlgorithm = ValidationAlgorithm.HMACSHA256 + }); + // + } + + public static void AddDataProtectionUseCustomCryptographicAlgorithms(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .UseCustomCryptographicAlgorithms( + new ManagedAuthenticatedEncryptorConfiguration + { + // A type that subclasses SymmetricAlgorithm + EncryptionAlgorithmType = typeof(Aes), + + // Specified in bits + EncryptionAlgorithmKeySize = 256, + + // A type that subclasses KeyedHashAlgorithm + ValidationAlgorithmType = typeof(HMACSHA256) + }); + // + } + +#pragma warning disable CA1416 // Validate platform compatibility + public static void AddDataProtectionUseCustomCryptographicAlgorithmsCngCbc(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .UseCustomCryptographicAlgorithms( + new CngCbcAuthenticatedEncryptorConfiguration + { + // Passed to BCryptOpenAlgorithmProvider + EncryptionAlgorithm = "AES", + EncryptionAlgorithmProvider = null, + + // Specified in bits + EncryptionAlgorithmKeySize = 256, + + // Passed to BCryptOpenAlgorithmProvider + HashAlgorithm = "SHA256", + HashAlgorithmProvider = null + }); + // + } + + public static void AddDataProtectionUseCustomCryptographicAlgorithmsCngGcm(WebApplicationBuilder builder) + { + // + builder.Services.AddDataProtection() + .UseCustomCryptographicAlgorithms( + new CngGcmAuthenticatedEncryptorConfiguration + { + // Passed to BCryptOpenAlgorithmProvider + EncryptionAlgorithm = "AES", + EncryptionAlgorithmProvider = null, + + // Specified in bits + EncryptionAlgorithmKeySize = 256 + }); + // + } +#pragma warning restore CA1416 // Validate platform compatibility +} diff --git a/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/SampleDbContext.cs b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/SampleDbContext.cs new file mode 100644 index 000000000000..c5d3ce6deb2d --- /dev/null +++ b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/Snippets/SampleDbContext.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; + +namespace DataProtectionConfigurationSample.Snippets; + +public class SampleDbContext : DbContext, IDataProtectionKeyContext +{ + public SampleDbContext(DbContextOptions dbContextOptions) + : base(dbContextOptions) { } + + // + public DbSet DataProtectionKeys { get; set; } = null!; + // +} diff --git a/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.Development.json b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.Development.json new file mode 100644 index 000000000000..0c208ae9181e --- /dev/null +++ b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.json b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.json new file mode 100644 index 000000000000..d76dbb9ea633 --- /dev/null +++ b/aspnetcore/security/data-protection/configuration/samples/9.x/DataProtectionConfigurationSample/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Microsoft.AspNetCore.DataProtection": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/aspnetcore/security/data-protection/configuration/samples/9/deleteKeys/ConsoleApp1.csproj b/aspnetcore/security/data-protection/configuration/samples/9.x/deleteKeys/ConsoleApp1.csproj similarity index 100% rename from aspnetcore/security/data-protection/configuration/samples/9/deleteKeys/ConsoleApp1.csproj rename to aspnetcore/security/data-protection/configuration/samples/9.x/deleteKeys/ConsoleApp1.csproj diff --git a/aspnetcore/security/data-protection/configuration/samples/9/deleteKeys/Program.cs b/aspnetcore/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs similarity index 100% rename from aspnetcore/security/data-protection/configuration/samples/9/deleteKeys/Program.cs rename to aspnetcore/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs From 1ed2cf8e05f0d1c62a297856df04a9f2e2bf64e2 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 9 Jun 2025 11:17:21 -0400 Subject: [PATCH 3/8] Updates --- aspnetcore/blazor/call-web-api.md | 2 +- .../blazor/security/blazor-web-app-with-entra.md | 2 +- .../data-protection/configuration/overview.md | 11 +++++++---- .../implementation/key-storage-providers.md | 13 ++++++++++++- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index a41acca5a0dc..15d11868d106 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -189,7 +189,7 @@ builder.Services.AddDataProtection() `{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The key identifier is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). > [!NOTE] > The preceding example uses locally (Development environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index da6241289dad..e58f8d4b4697 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -820,7 +820,7 @@ builder.Services.AddDataProtection() `{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). > [!NOTE] > The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). diff --git a/aspnetcore/security/data-protection/configuration/overview.md b/aspnetcore/security/data-protection/configuration/overview.md index 1fc76d4778c0..01fd811e3bdd 100644 --- a/aspnetcore/security/data-protection/configuration/overview.md +++ b/aspnetcore/security/data-protection/configuration/overview.md @@ -36,7 +36,7 @@ The following NuGet packages are required for the Data Protection extensions use ## Protect keys with Azure Key Vault (`ProtectKeysWithAzureKeyVault`) -To interact with [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) locally using developer credentials, either sign into your storage account in Visual Studio or sign in to Azure using the .NET CLI: +To interact with [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) locally using developer credentials, either sign into your storage account in Visual Studio or sign in with the [Azure CLI](/cli/azure/). If you haven't already installed the Azure CLI, see [How to install the Azure CLI](/cli/azure/install-azure-cli). You can execute the following command in the Developer PowerShell panel in Visual Studio or from a command shell when not using Visual Studio: ```azurecli az login @@ -50,9 +50,12 @@ When establishing the key vault in the Entra or Azure portal: * Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. + > [!NOTE] + > If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Key Vault Crypto User** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant. + * When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. -* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure you are using a new key vault key at the time of data protection key rotation. Also, either manually change the key identifier in the app or write custom code to adopt the latest key identifier for the latest key vault key when automatic key rotation occurs (such code is currently beyond the scope of this coverage). +* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Also, use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure you are using a new key vault key at the time of data protection key rotation. Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). You can also persist the key ring locally with [`PersistKeysToFileSystem`](xref:security/data-protection/implementation/key-storage-providers#file-system). @@ -88,7 +91,7 @@ builder.Services.AddDataProtection() `{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). For an app to communicate and authorize itself with Azure Key Vault, the [`Azure.Identity` NuGet package](https://www.nuget.org/packages/Azure.Identity/) must be referenced by the app. @@ -394,7 +397,7 @@ services.AddDataProtection() `{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). For an app to communicate and authorize itself with Azure Key Vault, the [`Azure.Identity` NuGet package](https://www.nuget.org/packages/Azure.Identity/) must be referenced by the app. diff --git a/aspnetcore/security/data-protection/implementation/key-storage-providers.md b/aspnetcore/security/data-protection/implementation/key-storage-providers.md index 9207fd51f8c2..4328a2268c72 100644 --- a/aspnetcore/security/data-protection/implementation/key-storage-providers.md +++ b/aspnetcore/security/data-protection/implementation/key-storage-providers.md @@ -34,6 +34,14 @@ The [`Azure.Extensions.AspNetCore.DataProtection.Blobs` NuGet package](https://w [!INCLUDE[](~/includes/package-reference.md)] +To interact with [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) locally using developer credentials, either sign into your storage account in Visual Studio or sign in with the [Azure CLI](/cli/azure/). If you haven't already installed the Azure CLI, see [How to install the Azure CLI](/cli/azure/install-azure-cli). You can execute the following command in the Developer PowerShell panel in Visual Studio or from a command shell when not using Visual Studio: + +```azurecli +az login +``` + +For more information, see [Sign-in to Azure using developer tooling](/dotnet/azure/sdk/authentication/local-development-dev-accounts#sign-in-to-azure-using-developer-tooling). + Configure Azure Blob Storage to maintain data protection keys: * Create an Azure storage account. @@ -52,6 +60,9 @@ Configure Azure Blob Storage to maintain data protection keys: * Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Storage Blob Data Contributor** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. + > [!NOTE] + > If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Storage Blob Data Contributor** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant. + To configure the Azure Blob Storage provider, call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). Other overloads are based on: @@ -59,7 +70,7 @@ Other overloads are based on: * A blob URI and storage shared key credential (). * A blob URI with a shared access signature (SAS). * A connection string, container name, and blob name. -* A blob client (). This approach is demonstrated later in this section. +* A blob client (). For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. From 931e5fd747e54247e6099e162bb9da936f3f8495 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:07:23 -0400 Subject: [PATCH 4/8] Updates --- aspnetcore/blazor/call-web-api.md | 9 ++--- .../security/blazor-web-app-with-entra.md | 30 ++++++++--------- .../aspnetcore-9/includes/delete_keys.md | 2 +- .../configuration/default-settings.md | 2 +- .../data-protection/configuration/overview.md | 33 +++++++++++-------- .../implementation/key-storage-providers.md | 12 ++++--- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 15d11868d106..8d43b4c45677 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -5,7 +5,7 @@ description: Learn how to call a web API from Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 06/06/2025 +ms.date: 06/09/2025 uid: blazor/call-web-api --- # Call a web API from ASP.NET Core Blazor @@ -138,7 +138,7 @@ Use a shared Data Protection key ring in production so that instances of the app > > 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 (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using their dedicated documentation sets, which are listed at the end of this section. +The following example shows how to use [Azure Blob Storage and Azure Key Vault (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using the Azure services' dedicated documentation sets, which are cross-linked at the end of this section. Add the following packages to the server project of the Blazor Web App: @@ -175,7 +175,7 @@ builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); -/* Blob URI with SAS approach +/* Alternative without using an Azure Managed Identity: SAS approach builder.Services.AddDataProtection() .SetApplicationName("BlazorWebAppEntra") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) @@ -187,7 +187,7 @@ builder.Services.AddDataProtection() `{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. -`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. +`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. `{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The key identifier is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). @@ -229,6 +229,7 @@ For more information, see the following resources: * [Host ASP.NET Core in a web farm: Data Protection](xref:host-and-deploy/web-farm#data-protection) * [Azure Key Vault documentation](/azure/key-vault/general/) * [Azure Storage documentation](/azure/storage/) +* [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli) ## Sample apps diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index e58f8d4b4697..ce08749dc931 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -5,7 +5,7 @@ description: Learn how to secure a Blazor Web App with Microsoft Entra ID. monikerRange: '>= aspnetcore-9.0' ms.author: wpickett ms.custom: mvc -ms.date: 06/06/2025 +ms.date: 06/09/2025 uid: blazor/security/blazor-web-app-entra zone_pivot_groups: blazor-web-app-entra-specification --- @@ -763,7 +763,7 @@ Use a shared Data Protection key ring in production so that instances of the app > > 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 (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using their dedicated documentation sets, which are listed at the end of this section. +The following example shows how to use [Azure Blob Storage and Azure Key Vault (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using the Azure services' dedicated documentation sets, which are cross-linked at the end of this section. Confirm the presence of the following packages in the server project of the Blazor Web App: @@ -799,12 +799,8 @@ else builder.Services.AddDataProtection() .SetApplicationName("BlazorWebAppEntra") - .PersistKeysToAzureBlobStorage( - new Uri("{BLOB URI}"), - credential) - .ProtectKeysWithAzureKeyVault( - new Uri("{KEY IDENTIFIER}"), - credential); + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); /* Blob URI with SAS approach builder.Services.AddDataProtection() @@ -818,12 +814,12 @@ builder.Services.AddDataProtection() `{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. -`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. +`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. `{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). > [!NOTE] -> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). +> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). Alternatively, you can configure the app to supply the values from app settings files using the JSON Configuration Provider. Add the following to the app settings file: @@ -845,10 +841,13 @@ Example `DataProtection` section: ```json "DataProtection": { "BlobUri": "https://contoso.blob.core.windows.net/data-protection/keys.xml", - "KeyIdentifier": "https://contoso.vault.azure.net/keys/data-protection/11112222bbbb3333cccc4444dddd5555" + "KeyIdentifier": "https://contoso.vault.azure.net/keys/data-protection" } ``` +> [!NOTE] +> The key identifier in the preceding example is *versionless*. There's no GUID key version on the end of the identifier. This is particularly important if you opt to configure automatic key rotation for the key. For more information, see [Configure cryptographic key auto-rotation in Azure Key Vault: Key rotation policy](/azure/key-vault/keys/how-to-configure-key-rotation#key-rotation-policy). + Make the following changes in the `Program` file: ```diff @@ -873,12 +872,8 @@ builder.Services.Configure( - builder.Services.AddDataProtection() - .SetApplicationName("BlazorWebAppEntra") -- .PersistKeysToAzureBlobStorage( -- new Uri("{BLOB URI}"), -- credential) -- .ProtectKeysWithAzureKeyVault( -- new Uri("{KEY IDENTIFIER}"), -- credential); +- .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) +- .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); ``` Add the following code where services are configured in the `Program` file: @@ -908,6 +903,7 @@ For more information on using a shared Data Protection key ring and key storage * * [Azure Key Vault documentation](/azure/key-vault/general/) * [Azure Storage documentation](/azure/storage/) +* [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli) ## Redirect to the home page on logout diff --git a/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md b/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md index 42ed4405c19d..0ab19bf5f1be 100644 --- a/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md +++ b/aspnetcore/release-notes/aspnetcore-9/includes/delete_keys.md @@ -2,4 +2,4 @@ Prior to .NET 9, data protection keys were ___not___ deletable by design, to prevent data loss. Deleting a key renders its protected data irretrievable. Given their small size, the accumulation of these keys generally posed minimal impact. However, to accommodate extremely long-running services, we have introduced the option to delete keys. Generally, only old keys should be deleted. Only delete keys when you can accept the risk of data loss in exchange for storage savings. We recommend data protection keys should ___not___ be deleted. -:::code language="csharp" source="~/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs" ::: +:::code language="csharp" source="~/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs"::: diff --git a/aspnetcore/security/data-protection/configuration/default-settings.md b/aspnetcore/security/data-protection/configuration/default-settings.md index 140c0f766367..1786b1c01f9f 100644 --- a/aspnetcore/security/data-protection/configuration/default-settings.md +++ b/aspnetcore/security/data-protection/configuration/default-settings.md @@ -58,7 +58,7 @@ Deleting a key makes its protected data permanently inaccessible. To mitigate th We recommend not deleting data protection keys. -:::code language="csharp" source="~/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs" ::: +:::code language="csharp" source="~/security/data-protection/configuration/samples/9.x/deleteKeys/Program.cs"::: ## Additional resources diff --git a/aspnetcore/security/data-protection/configuration/overview.md b/aspnetcore/security/data-protection/configuration/overview.md index 01fd811e3bdd..3439b6801bd2 100644 --- a/aspnetcore/security/data-protection/configuration/overview.md +++ b/aspnetcore/security/data-protection/configuration/overview.md @@ -5,7 +5,7 @@ description: Learn how to configure Data Protection in ASP.NET Core. monikerRange: '>= aspnetcore-3.1' ms.author: tdykstra ms.custom: mvc -ms.date: 06/06/2025 +ms.date: 06/09/2025 uid: security/data-protection/configuration/overview --- # Configure ASP.NET Core Data Protection @@ -46,26 +46,27 @@ For more information, see [Sign-in to Azure using developer tooling](/dotnet/azu When establishing the key vault in the Entra or Azure portal: -* Configure the key vault to Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. +* Configure the key vault to use Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. -* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. > [!NOTE] > If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Key Vault Crypto User** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant. * When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. -* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Also, use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure you are using a new key vault key at the time of data protection key rotation. +* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Also, use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure that a new key vault key is used at the time of data protection key rotation. -Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). You can also persist the key ring locally with [`PersistKeysToFileSystem`](xref:security/data-protection/implementation/key-storage-providers#file-system). +Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . -For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. +For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/) and [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. In the `Program` file where services are registered: ```csharp +// Recommended: Azure Managed Identity approach TokenCredential? credential; if (builder.Environment.IsProduction()) @@ -82,14 +83,18 @@ builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); -/* Alternative without using an Azure Managed Identity +/* Alternative without using an Azure Managed Identity: SAS approach builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); */ ``` -`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. +`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). + +`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. + +`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. `{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). @@ -98,7 +103,7 @@ For an app to communicate and authorize itself with Azure Key Vault, the [`Azure [!INCLUDE[](~/includes/package-reference.md)] > [!NOTE] -> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). +> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). If the app uses the older Azure packages (`Microsoft.AspNetCore.DataProtection.AzureStorage` and `Microsoft.AspNetCore.DataProtection.AzureKeyVault`), we recommend ***removing*** these references and upgrading to the [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) packages. The newer packages address key security and stability issues. @@ -355,19 +360,19 @@ For more information, see [Sign-in to Azure using developer tooling](/dotnet/azu When establishing the key vault in the Entra or Azure portal: -* Configure the key vault to Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. +* Configure the key vault to use Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. -* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. * When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. -* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure you are using a new key vault key at the time of data protection key rotation. Also, either manually change the key identifier in the app or write custom code to adopt the latest key identifier for the latest key vault key when automatic key rotation occurs (such code is currently beyond the scope of this coverage). +* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure that a new key vault key is used at the time of data protection key rotation. Also, either manually change the key identifier in the app or write custom code to adopt the latest key identifier for the latest key vault key when automatic key rotation occurs (such code is currently beyond the scope of this coverage). -Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). You can also persist the key ring locally with [`PersistKeysToFileSystem`](xref:security/data-protection/implementation/key-storage-providers#file-system). +Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . -For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. +For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/) and [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. In `Startup.ConfigureServices`: diff --git a/aspnetcore/security/data-protection/implementation/key-storage-providers.md b/aspnetcore/security/data-protection/implementation/key-storage-providers.md index 4328a2268c72..355a80fe7b55 100644 --- a/aspnetcore/security/data-protection/implementation/key-storage-providers.md +++ b/aspnetcore/security/data-protection/implementation/key-storage-providers.md @@ -3,7 +3,7 @@ title: Key storage providers in ASP.NET Core author: rick-anderson description: Learn about key storage providers in ASP.NET Core and how to configure key storage locations. ms.author: riande -ms.date: 06/06/2025 +ms.date: 06/09/2025 uid: security/data-protection/implementation/key-storage-providers --- @@ -58,7 +58,7 @@ Configure Azure Blob Storage to maintain data protection keys: * Upload the key file to the container of the storage account. Use the context menu's **View/edit** command at the end of the key row in the portal to confirm that the blob contains the preceding content. -* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Storage Blob Data Contributor** role. Assign the Managed Identity to the App Service hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Storage Blob Data Contributor** role. Assign the Managed Identity to the App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. > [!NOTE] > If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Storage Blob Data Contributor** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant. @@ -79,6 +79,7 @@ For more information on the Azure SDK's API and authentication, see [Authenticat In the `Program` file where services are registered: ```csharp +// Recommended: Azure Managed Identity approach TokenCredential? credential; if (builder.Environment.IsProduction()) @@ -94,7 +95,7 @@ else builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential); -/* Alternative without using an Azure Managed Identity +/* Alternative without using an Azure Managed Identity: SAS approach builder.Services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")); */ @@ -107,6 +108,7 @@ builder.Services.AddDataProtection() In `Startup.ConfigureServices`: ```csharp +// Recommended: Azure Managed Identity approach TokenCredential? credential; if (_env.IsProduction()) @@ -122,7 +124,7 @@ else services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential); -/* Alternative without using an Azure Managed Identity +/* Alternative without using an Azure Managed Identity: SAS approach services.AddDataProtection() .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")); */ @@ -136,7 +138,7 @@ Example: > :::no-loc text="https://contoso.blob.core.windows.net/data-protection/keys.xml"::: -`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. In the following example, the container name is `data-protection`, and the storage account name is `contoso`. The key file is named `keys.xml`. The shared access signature (SAS) query string is at the end of the URI (`{SHARED ACCESS SIGNATURE}` placeholder). +`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. In the following example, the container name is `data-protection`, and the storage account name is `contoso`. The key file is named `keys.xml`. The shared access signature (SAS) query string is at the end of the URI (`{SHARED ACCESS SIGNATURE}` placeholder). Example: From 12ddec0d83d4b2b35c1e4e3e31e6e15ada21dca4 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Tue, 10 Jun 2025 16:09:40 -0400 Subject: [PATCH 5/8] Updates --- aspnetcore/blazor/call-web-api.md | 17 +++------ .../security/blazor-web-app-with-entra.md | 13 ++----- .../data-protection/configuration/overview.md | 30 ++++++++++----- .../implementation/key-storage-providers.md | 38 ++++++++++++++----- 4 files changed, 56 insertions(+), 42 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 8d43b4c45677..2ea2bafa7197 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -124,7 +124,7 @@ To configure a production distributed cache provider, see , 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`. @@ -138,7 +138,7 @@ Use a shared Data Protection key ring in production so that instances of the app > > 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 (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using the Azure services' dedicated documentation sets, which are cross-linked at the end of this section. +The following example shows how to use [Azure Blob Storage and Azure Key Vault (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using the Azure services' dedicated documentation sets, which are linked at the end of this section. Add the following packages to the server project of the Blazor Web App: @@ -171,23 +171,16 @@ else } builder.Services.AddDataProtection() - .SetApplicationName("BlazorWebAppEntra") + .SetApplicationName("{APPLICATION NAME}") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); - -/* Alternative without using an Azure Managed Identity: SAS approach -builder.Services.AddDataProtection() - .SetApplicationName("BlazorWebAppEntra") - .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) - .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); -*/ ``` `{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). -`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. -`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. +`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. `{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The key identifier is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index ce08749dc931..4648dc9ced65 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -763,7 +763,7 @@ Use a shared Data Protection key ring in production so that instances of the app > > 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 (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using the Azure services' dedicated documentation sets, which are cross-linked at the end of this section. +The following example shows how to use [Azure Blob Storage and Azure Key Vault (`PersistKeysToAzureBlobStorage`/`ProtectKeysWithAzureKeyVault`)](xref:security/data-protection/configuration/overview#protect-keys-with-azure-key-vault-protectkeyswithazurekeyvault) for the shared key ring. The service configurations are base case scenarios for demonstration purposes. Before deploying production apps, familiarize yourself with the Azure services and adopt best practices using the Azure services' dedicated documentation sets, which are linked at the end of this section. Confirm the presence of the following packages in the server project of the Blazor Web App: @@ -801,21 +801,14 @@ builder.Services.AddDataProtection() .SetApplicationName("BlazorWebAppEntra") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); - -/* Blob URI with SAS approach -builder.Services.AddDataProtection() - .SetApplicationName("BlazorWebAppEntra") - .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) - .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); -*/ ``` +You can pass any app name to . Just confirm that all app deployments use the same value. + `{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). `{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. -`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. - `{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). > [!NOTE] diff --git a/aspnetcore/security/data-protection/configuration/overview.md b/aspnetcore/security/data-protection/configuration/overview.md index 3439b6801bd2..f444cbb9721b 100644 --- a/aspnetcore/security/data-protection/configuration/overview.md +++ b/aspnetcore/security/data-protection/configuration/overview.md @@ -59,7 +59,7 @@ When establishing the key vault in the Entra or Azure portal: Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). -To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . +To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC in production () or a during development and testing. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/) and [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. @@ -80,23 +80,18 @@ else } builder.Services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); - -/* Alternative without using an Azure Managed Identity: SAS approach -builder.Services.AddDataProtection() - .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) - .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); -*/ ``` `{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. + `{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. -`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. - -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable automatic rotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). For an app to communicate and authorize itself with Azure Key Vault, the [`Azure.Identity` NuGet package](https://www.nuget.org/packages/Azure.Identity/) must be referenced by the app. @@ -107,6 +102,21 @@ For an app to communicate and authorize itself with Azure Key Vault, the [`Azure If the app uses the older Azure packages (`Microsoft.AspNetCore.DataProtection.AzureStorage` and `Microsoft.AspNetCore.DataProtection.AzureKeyVault`), we recommend ***removing*** these references and upgrading to the [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) packages. The newer packages address key security and stability issues. +**Alternative shared-access signature (SAS) approach**: As an alternative to using a Managed Identity for access to the key blob in Azure Blob Storage, you can call the overload that accepts a blob URI with a SAS token. The following example continues to use either a (production) or (development and testing) for its , as seen in the preceding example: + +```csharp +builder.Services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); +``` + +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. + +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. + +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable automatic rotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). + ## Persist keys to the file system (`PersistKeysToFileSystem`) To store keys on a UNC share instead of at the *%LOCALAPPDATA%* default location, configure the system with : diff --git a/aspnetcore/security/data-protection/implementation/key-storage-providers.md b/aspnetcore/security/data-protection/implementation/key-storage-providers.md index 355a80fe7b55..d2f5e7facfb6 100644 --- a/aspnetcore/security/data-protection/implementation/key-storage-providers.md +++ b/aspnetcore/security/data-protection/implementation/key-storage-providers.md @@ -93,12 +93,22 @@ else } builder.Services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential); +``` + +`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). + +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. + +`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. -/* Alternative without using an Azure Managed Identity: SAS approach +**Alternative shared-access signature (SAS) approach**: As an alternative to using a Managed Identity for access to the key blob in Azure Blob Storage, you can call the overload that accepts a blob URI with a SAS token: + +```csharp builder.Services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")); -*/ ``` :::moniker-end @@ -122,30 +132,38 @@ else } services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential); - -/* Alternative without using an Azure Managed Identity: SAS approach -services.AddDataProtection() - .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")); -*/ ``` `{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. + `{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. Example: > :::no-loc text="https://contoso.blob.core.windows.net/data-protection/keys.xml"::: -`{BLOB URI WITH SAS}`: *This approach only applies if you opt not to use an Azure Managed Identity.* The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. In the following example, the container name is `data-protection`, and the storage account name is `contoso`. The key file is named `keys.xml`. The shared access signature (SAS) query string is at the end of the URI (`{SHARED ACCESS SIGNATURE}` placeholder). +**Alternative shared-access signature (SAS) approach**: As an alternative to using a Managed Identity for access to the key blob in Azure Blob Storage, you can call the overload that accepts a blob URI with a SAS token: + +```csharp +services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")); +``` + +:::moniker-end + +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. + +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. In the following example, the container name is `data-protection`, and the storage account name is `contoso`. The key file is named `keys.xml`. The shared access signature (SAS) query string is at the end of the URI (`{SHARED ACCESS SIGNATURE}` placeholder). Example: > :::no-loc text="https://contoso.blob.core.windows.net/data-protection/keys.xml{SHARED ACCESS SIGNATURE}"::: -:::moniker-end - If the web app is running as an Azure service, a connection string can be used to authenticate to Azure Storage using , as seen in the following example. [!INCLUDE [managed-identities](~/includes/managed-identities-conn-strings.md)] From 774b20f684b4dab48c5e8ede3d870393364cfd1a Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:08:00 -0400 Subject: [PATCH 6/8] Updates --- aspnetcore/blazor/call-web-api.md | 5 +- .../security/blazor-web-app-with-entra.md | 7 +- .../data-protection/configuration/overview.md | 67 ++++++++++++------- .../implementation/key-storage-providers.md | 30 +++++---- 4 files changed, 64 insertions(+), 45 deletions(-) diff --git a/aspnetcore/blazor/call-web-api.md b/aspnetcore/blazor/call-web-api.md index 2ea2bafa7197..1721bdde612c 100644 --- a/aspnetcore/blazor/call-web-api.md +++ b/aspnetcore/blazor/call-web-api.md @@ -5,7 +5,7 @@ description: Learn how to call a web API from Blazor apps. monikerRange: '>= aspnetcore-3.1' ms.author: wpickett ms.custom: mvc -ms.date: 06/09/2025 +ms.date: 06/11/2025 uid: blazor/call-web-api --- # Call a web API from ASP.NET Core Blazor @@ -157,7 +157,6 @@ Configure Azure Key Vault to encrypt the data protection keys at rest. Follow th Use the following code in the `Program` file where services are registered: ```csharp -// Recommended: Azure Managed Identity approach TokenCredential? credential; if (builder.Environment.IsProduction()) @@ -185,7 +184,7 @@ builder.Services.AddDataProtection() `{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The key identifier is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). > [!NOTE] -> The preceding example uses locally (Development environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). +> In non-Production environments, the preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as the shown in the preceding example. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). Inject and call when calling on behalf of a user: diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index 4648dc9ced65..5d1edb652946 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -5,7 +5,7 @@ description: Learn how to secure a Blazor Web App with Microsoft Entra ID. monikerRange: '>= aspnetcore-9.0' ms.author: wpickett ms.custom: mvc -ms.date: 06/09/2025 +ms.date: 06/11/2025 uid: blazor/security/blazor-web-app-entra zone_pivot_groups: blazor-web-app-entra-specification --- @@ -784,7 +784,6 @@ Configure Azure Key Vault to encrypt the data protection keys at rest. Follow th Use the following code in the `Program` file where services are registered: ```csharp -// Recommended: Azure Managed Identity approach TokenCredential? credential; if (builder.Environment.IsProduction()) @@ -812,7 +811,7 @@ You can pass any app name to [!NOTE] -> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). +> In non-Production environments, the preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). Alternatively, you can configure the app to supply the values from app settings files using the JSON Configuration Provider. Add the following to the app settings file: @@ -875,7 +874,7 @@ Add the following code where services are configured in the `Program` file: var config = builder.Configuration.GetSection("DataProtection"); builder.Services.AddDataProtection() - .SetApplicationName("BlazorSample") + .SetApplicationName("BlazorWebAppEntra") .PersistKeysToAzureBlobStorage( new Uri(config.GetValue("BlobUri") ?? throw new Exception("Missing Blob URI")), diff --git a/aspnetcore/security/data-protection/configuration/overview.md b/aspnetcore/security/data-protection/configuration/overview.md index f444cbb9721b..b3614f6e761f 100644 --- a/aspnetcore/security/data-protection/configuration/overview.md +++ b/aspnetcore/security/data-protection/configuration/overview.md @@ -5,7 +5,7 @@ description: Learn how to configure Data Protection in ASP.NET Core. monikerRange: '>= aspnetcore-3.1' ms.author: tdykstra ms.custom: mvc -ms.date: 06/09/2025 +ms.date: 06/11/2025 uid: security/data-protection/configuration/overview --- # Configure ASP.NET Core Data Protection @@ -48,7 +48,7 @@ When establishing the key vault in the Entra or Azure portal: * Configure the key vault to use Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. -* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the Azure App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. > [!NOTE] > If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Key Vault Crypto User** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant. @@ -66,7 +66,6 @@ For more information on the Azure SDK's API and authentication, see [Authenticat In the `Program` file where services are registered: ```csharp -// Recommended: Azure Managed Identity approach TokenCredential? credential; if (builder.Environment.IsProduction()) @@ -98,7 +97,7 @@ For an app to communicate and authorize itself with Azure Key Vault, the [`Azure [!INCLUDE[](~/includes/package-reference.md)] > [!NOTE] -> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). +> In non-Production environments, the preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). If the app uses the older Azure packages (`Microsoft.AspNetCore.DataProtection.AzureStorage` and `Microsoft.AspNetCore.DataProtection.AzureKeyVault`), we recommend ***removing*** these references and upgrading to the [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) packages. The newer packages address key security and stability issues. @@ -214,13 +213,16 @@ For more information on how the discriminator is used, see the following section > In .NET 6, normalizes the content root path to end with a . For example, on Windows the content root path ends in `\` and on Linux `/`. Other hosts don't normalize the path. Most apps migrating from or won't share the same app name because they won't have the terminating `DirectorySeparatorChar`. To work around this issue, remove the directory separator character and set the app name manually, as shown in the following code: > > ```csharp -> using Microsoft.AspNetCore.DataProtection; > using System.Reflection; +> using Microsoft.AspNetCore.DataProtection; > > var builder = WebApplication.CreateBuilder(args); -> var trimmedContentRootPath = builder.Environment.ContentRootPath.TrimEnd(Path.DirectorySeparatorChar); -> builder.Services.AddDataProtection() -> .SetApplicationName(trimmedContentRootPath); +> +> var trimmedContentRootPath = +> builder.Environment.ContentRootPath.TrimEnd(Path.DirectorySeparatorChar); +> +> builder.Services.AddDataProtection().SetApplicationName(trimmedContentRootPath); +> > var app = builder.Build(); > > app.MapGet("/", () => Assembly.GetEntryAssembly()!.GetName().Name); @@ -360,7 +362,7 @@ The following NuGet packages are required for the Data Protection extensions use ## Protect keys with Azure Key Vault (`ProtectKeysWithAzureKeyVault`) -To interact with [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) locally using developer credentials, either sign into your storage account in Visual Studio or sign in to Azure using the .NET CLI: +To interact with [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) locally using developer credentials, either sign into your storage account in Visual Studio or sign in with the [Azure CLI](/cli/azure/). If you haven't already installed the Azure CLI, see [How to install the Azure CLI](/cli/azure/install-azure-cli). You can execute the following command in the Developer PowerShell panel in Visual Studio or from a command shell when not using Visual Studio: ```azurecli az login @@ -372,24 +374,27 @@ When establishing the key vault in the Entra or Azure portal: * Configure the key vault to use Azure role-based access control (RABC). If you aren't operating on an [Azure Virtual Network](/azure/virtual-network/virtual-networks-overview), including for local development and testing, confirm that public access on the **Networking** step is **enabled** (checked). Enabling public access only exposes the key vault endpoint. Authenticated accounts are still required for access. -* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Key Vault Crypto User** role. Assign the Managed Identity to the Azure App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. + + > [!NOTE] + > If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Key Vault Crypto User** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant. * When key encryption is active, keys in the key file include the comment, ":::no-loc text="This key is encrypted with Azure Key Vault.":::" After starting the app, select the **View/edit** command from the context menu at the end of the key row to confirm that a key is present with key vault security applied. -* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure that a new key vault key is used at the time of data protection key rotation. Also, either manually change the key identifier in the app or write custom code to adopt the latest key identifier for the latest key vault key when automatic key rotation occurs (such code is currently beyond the scope of this coverage). +* Optionally, you can enable automatic key vault key rotation without concern about decrypting payloads with data protection keys based on expired/rotated key vault keys. Each generated data protection key includes a reference to the key vault key used to encrypted it. Just make sure that you retain expired key vault keys, don't delete them in the key vault. Also, use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). Use a similar rotation period for both keys with the key vault key rotating more frequently than the data protection key to ensure that a new key vault key is used at the time of data protection key rotation. Protecting keys with Azure Key Vault implements an that disables automatic data protection settings, including the key ring storage location. To configure the Azure Blob Storage provider to store the keys in blob storage, follow the guidance in and call one of the overloads in the app. The following example uses the overload that accepts a blob URI and token credential (), relying on an Azure Managed Identity for role-based access control (RBAC). -To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . +To configure the Azure Key Vault provider, call one of the overloads. The following example uses the overload that accepts key identifier and token credential (), relying on a Managed Identity for RBAC in production () or a during development and testing. Other overloads accept either a key vault client or an app client ID with client secret. For more information, see . For more information on the Azure SDK's API and authentication, see [Authenticate .NET apps to Azure services using the Azure Identity library](/dotnet/azure/sdk/authentication/) and [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli). For logging guidance, see [Logging with the Azure SDK for .NET: Logging without client registration](/dotnet/azure/sdk/logging#logging-without-client-registration). For apps using dependency injection, an app can call , passing `true` for `enableLogForwarding`, to create and wire up the logging infrastructure. -In `Startup.ConfigureServices`: +In the `Program` file where services are registered: ```csharp TokenCredential? credential; -if (_env.IsProduction()) +if (builder.Environment.IsProduction()) { credential = new ManagedIdentityCredential("{MANAGED IDENTITY CLIENT ID}"); } @@ -400,29 +405,43 @@ else } services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI}"), credential) .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); - -/* Alternative without using an Azure Managed Identity -services.AddDataProtection() - .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) - .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); -*/ ``` -`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. +`{MANAGED IDENTITY CLIENT ID}`: The Azure Managed Identity Client ID (GUID). -`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable autorotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. + +`{BLOB URI}`: Full URI to the key file. The URI is generated by Azure Storage when you create the key file. Do not use a SAS. + +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable automatic rotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). For an app to communicate and authorize itself with Azure Key Vault, the [`Azure.Identity` NuGet package](https://www.nuget.org/packages/Azure.Identity/) must be referenced by the app. [!INCLUDE[](~/includes/package-reference.md)] > [!NOTE] -> The preceding example uses locally (non-Production environment) to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. When moving to production, an alternative is a better choice, such as . For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). +> In non-Production environments, the preceding example uses to simplify authentication while developing apps that deploy to Azure by combining credentials used in Azure hosting environments with credentials used in local development. For more information, see [Authenticate Azure-hosted .NET apps to Azure resources using a system-assigned managed identity](/dotnet/azure/sdk/authentication/system-assigned-managed-identity). If the app uses the older Azure packages (`Microsoft.AspNetCore.DataProtection.AzureStorage` and `Microsoft.AspNetCore.DataProtection.AzureKeyVault`), we recommend ***removing*** these references and upgrading to the [`Azure.Extensions.AspNetCore.DataProtection.Blobs`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Blobs) and [`Azure.Extensions.AspNetCore.DataProtection.Keys`](https://www.nuget.org/packages/Azure.Extensions.AspNetCore.DataProtection.Keys) packages. The newer packages address key security and stability issues. +**Alternative shared-access signature (SAS) approach**: As an alternative to using a Managed Identity for access to the key blob in Azure Blob Storage, you can call the overload that accepts a blob URI with a SAS token. The following example continues to use either a (production) or (development and testing) for its , as seen in the preceding example: + +```csharp +services.AddDataProtection() + .SetApplicationName("{APPLICATION NAME}") + .PersistKeysToAzureBlobStorage(new Uri("{BLOB URI WITH SAS}")) + .ProtectKeysWithAzureKeyVault(new Uri("{KEY IDENTIFIER}"), credential); +``` + +`{APPLICATION NAME}`: sets the unique name of this app within the data protection system. The value should match across deployments of the app. + +`{BLOB URI WITH SAS}`: The full URI where the key file should be stored with the SAS token as a query string parameter. The URI is generated by Azure Storage when you request a SAS for the uploaded key file. + +`{KEY IDENTIFIER}`: Azure Key Vault key identifier used for key encryption. An access policy allows the application to access the key vault with `Get`, `Unwrap Key`, and `Wrap Key` permissions. The version of the key is obtained from the key in the Entra or Azure portal after it's created. If you enable automatic rotation of the key vault key, make sure that you use a versionless key identifier in the app's key vault configuration, where no key GUID is placed at the end of the identifier (example: `https://contoso.vault.azure.net/keys/data-protection`). + ## Persist keys to the file system (`PersistKeysToFileSystem`) To store keys on a UNC share instead of at the *%LOCALAPPDATA%* default location, configure the system with : @@ -529,7 +548,7 @@ To share protected payloads among apps: public void ConfigureServices(IServiceCollection services) { services.AddDataProtection() - .SetApplicationName("shared app name"); + .SetApplicationName("{APPLICATION NAME}"); } ``` diff --git a/aspnetcore/security/data-protection/implementation/key-storage-providers.md b/aspnetcore/security/data-protection/implementation/key-storage-providers.md index d2f5e7facfb6..22ecbb53dc40 100644 --- a/aspnetcore/security/data-protection/implementation/key-storage-providers.md +++ b/aspnetcore/security/data-protection/implementation/key-storage-providers.md @@ -2,8 +2,9 @@ title: Key storage providers in ASP.NET Core author: rick-anderson description: Learn about key storage providers in ASP.NET Core and how to configure key storage locations. +monikerRange: '>= aspnetcore-3.1' ms.author: riande -ms.date: 06/09/2025 +ms.date: 06/11/2025 uid: security/data-protection/implementation/key-storage-providers --- @@ -48,17 +49,20 @@ Configure Azure Blob Storage to maintain data protection keys: * Create a container to hold the data protection key file. -* Use a text editor to create an XML key file on your local machine: - - ```xml - - - - ``` - -* Upload the key file to the container of the storage account. Use the context menu's **View/edit** command at the end of the key row in the portal to confirm that the blob contains the preceding content. - -* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Storage Blob Data Contributor** role. Assign the Managed Identity to the App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. +* We recommend using Azure Managed Identity and role-based access control (RBAC) to access the key storage blob. ***You don't need to create a key file and upload it to the container of the storage account.*** The framework creates the file for you. To inspect the contents of a key file, use the context menu's **View/edit** command at the end of a key row in the portal. + +> ![NOTE] +> If you plan to use a blob URI with a shared access signature (SAS) instead of a Managed Identity, use a text editor to create an XML key file on your local machine: +> +> ```xml +> +> +> +> ``` +> +> Upload the key file to the container of the storage account. Use the context menu's **View/edit** command at the end of the key row in the portal to confirm that the blob contains the preceding content. By creating the file manually, you're able to obtain the blob URI with SAS from the portal for configuring the app in a later step. + +* Create an Azure Managed Identity (or add a role to the existing Managed Identity that you plan to use) with the **Storage Blob Data Contributor** role. Assign the Managed Identity to the Azure App Service that's hosting the deployment: **Settings** > **Identity** > **User assigned** > **Add**. > [!NOTE] > If you also plan to run an app locally with an authorized user for blob access using the [Azure CLI](/cli/azure/) or Visual Studio's Azure Service Authentication, add your developer Azure user account in **Access Control (IAM)** with the **Storage Blob Data Contributor** role. If you want to use the Azure CLI through Visual Studio, execute the `az login` command from the Developer PowerShell panel and follow the prompts to authenticate with the tenant. @@ -79,7 +83,6 @@ For more information on the Azure SDK's API and authentication, see [Authenticat In the `Program` file where services are registered: ```csharp -// Recommended: Azure Managed Identity approach TokenCredential? credential; if (builder.Environment.IsProduction()) @@ -118,7 +121,6 @@ builder.Services.AddDataProtection() In `Startup.ConfigureServices`: ```csharp -// Recommended: Azure Managed Identity approach TokenCredential? credential; if (_env.IsProduction()) From 9bdb413d6311a446225e0192060b82b097f5629d Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 11 Jun 2025 11:12:20 -0400 Subject: [PATCH 7/8] Updates --- .../data-protection/implementation/key-storage-providers.md | 1 - 1 file changed, 1 deletion(-) diff --git a/aspnetcore/security/data-protection/implementation/key-storage-providers.md b/aspnetcore/security/data-protection/implementation/key-storage-providers.md index 22ecbb53dc40..b4937cf5e871 100644 --- a/aspnetcore/security/data-protection/implementation/key-storage-providers.md +++ b/aspnetcore/security/data-protection/implementation/key-storage-providers.md @@ -2,7 +2,6 @@ title: Key storage providers in ASP.NET Core author: rick-anderson description: Learn about key storage providers in ASP.NET Core and how to configure key storage locations. -monikerRange: '>= aspnetcore-3.1' ms.author: riande ms.date: 06/11/2025 uid: security/data-protection/implementation/key-storage-providers From 7c95dfc7fc3394247c38d401c7931d6e087c2cc1 Mon Sep 17 00:00:00 2001 From: guardrex <1622880+guardrex@users.noreply.github.com> Date: Wed, 18 Jun 2025 09:05:02 -0400 Subject: [PATCH 8/8] Updates --- .../security/blazor-web-app-with-entra.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/aspnetcore/blazor/security/blazor-web-app-with-entra.md b/aspnetcore/blazor/security/blazor-web-app-with-entra.md index 5d1edb652946..718465d473ce 100644 --- a/aspnetcore/blazor/security/blazor-web-app-with-entra.md +++ b/aspnetcore/blazor/security/blazor-web-app-with-entra.md @@ -897,6 +897,27 @@ For more information on using a shared Data Protection key ring and key storage * [Azure Storage documentation](/azure/storage/) * [Provide access to Key Vault keys, certificates, and secrets with Azure role-based access control](/azure/key-vault/general/rbac-guide?tabs=azure-cli) +## YARP forwarder destination prefix + +The Blazor Web App server project's YARP forwarder, where the user's access token is attached to the `MinimalApiJwt` web API call, specifies a destination prefix of `https://weatherapi`. This value matches the project name passed to in the `Program` file of the `Aspire.AppHost` project. + +Forwarder in the Blazor Web App server project (`BlazorWebAppEntra`): + +```csharp +app.MapForwarder("/weather-forecast", "https://weatherapi", transformBuilder => +{ + ... +}).RequireAuthorization(); +``` + +Matching project name in the `Program` file of the Aspire App Host project (`Aspire.AppHost`): + +```csharp +var weatherApi = builder.AddProject("weatherapi"); +``` + +There's no need to change the destination prefix of the YARP forwarder when deploying the Blazor Web App to production. The Microsoft Identity Web Downstream API package uses the base URI passed via configuration to make the web API call from the `ServerWeatherForecaster`, not the destination prefix of the YARP forwarder. In production, the YARP forwarder merely transforms the request, adding the user's access token. + ## 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.