-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New auth best practices doc (#44284)
* New auth best practices doc --------- Co-authored-by: Christopher Scott <[email protected]> Co-authored-by: Scott Addie <[email protected]>
- Loading branch information
1 parent
0490d52
commit 1c007f0
Showing
8 changed files
with
204 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
74 changes: 74 additions & 0 deletions
74
docs/azure/sdk/authentication/authentication-best-practices.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
--- | ||
title: Authentication best practices with the Azure Identity library for .NET | ||
description: This article describes authentication best practices to follow when using the Azure Identity library for .NET. | ||
ms.topic: conceptual | ||
ms.date: 01/15/2025 | ||
--- | ||
|
||
# Authentication best practices with the Azure Identity library for .NET | ||
|
||
This article offers guidelines to help you maximize the performance and reliability of your .NET apps when authenticating to Azure services. To make the most of the Azure Identity library for .NET, it's important to understand potential issues and mitigation techniques. | ||
|
||
## Use deterministic credentials in production environments | ||
|
||
[`DefaultAzureCredential`](/dotnet/azure/sdk/authentication/credential-chains?tabs=dac#defaultazurecredential-overview) is the most approachable way to get started with the Azure Identity library, but that convenience also introduces certain tradeoffs. Most notably, the specific credential in the chain that will succeed and be used for request authentication can't be guaranteed ahead of time. In a production environment, this unpredictability can introduce significant and sometimes subtle problems. | ||
|
||
For example, consider the following hypothetical sequence of events: | ||
|
||
1. An organization's security team mandates all apps use managed identity to authenticate to Azure resources. | ||
1. For months, a .NET app hosted on an Azure Virtual Machine (VM) successfully uses `DefaultAzureCredential` to authenticate via managed identity. | ||
1. Without telling the support team, a developer installs the Azure CLI on that VM and runs the `az login` command to authenticate to Azure. | ||
1. Due to a separate configuration change in the Azure environment, authentication via the original managed identity unexpectedly begins to fail silently. | ||
1. `DefaultAzureCredential` skips the failed `ManagedIdentityCredential` and searches for the next available credential, which is `AzureCliCredential`. | ||
1. The application starts utilizing the Azure CLI credentials rather than the managed identity, which may fail or result in unexpected elevation or reduction of privileges. | ||
|
||
To prevent these types of subtle issues or silent failures in production apps, strongly consider moving from `DefaultAzureCredential` to one of the following deterministic solutions: | ||
|
||
- A specific `TokenCredential` implementation, such as `ManagedIdentityCredential`. See the [**Derived** list](/dotnet/api/azure.core.tokencredential?view=azure-dotnet&preserve-view=true#definition) for options. | ||
- A pared-down `ChainedTokenCredential` implementation optimized for the Azure environment in which your app runs. `ChainedTokenCredential` essentially creates a specific allow-list of acceptable credential options, such as `ManagedIdentity` for production and `VisualStudioCredential` for development. | ||
|
||
For example, consider the following `DefaultAzureCredential` configuration in an ASP.NET Core project: | ||
|
||
:::code language="csharp" source="../snippets/authentication/credential-chains/Program.cs" id="snippet_Dac" highlight="6,7"::: | ||
|
||
Replace the preceding code with a `ChainedTokenCredential` implementation that specifies only the necessary credentials: | ||
|
||
:::code language="csharp" source="../snippets/authentication/credential-chains/Program.cs" id="snippet_Ctc" highlight="6-8"::: | ||
|
||
In this example, `ManagedIdentityCredential` would be automatically discovered in production, while `VisualStudioCredential` would work in local development environments. | ||
|
||
## Reuse credential instances | ||
|
||
Reuse credential instances when possible to improve app resilience and reduce the number of access token requests issued to Microsoft Entra ID. When a credential is reused, an attempt is made to fetch a token from the app token cache managed by the underlying MSAL dependency. For more information, see [Token caching in the Azure Identity client library](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/identity/Azure.Identity/samples/TokenCache.md). | ||
|
||
> [!IMPORTANT] | ||
> A high-volume app that doesn't reuse credentials may encounter HTTP 429 throttling responses from Microsoft Entra ID, which can lead to app outages. | ||
The recommended credential reuse strategy differs by .NET application type. | ||
|
||
# [ASP.NET Core](#tab/aspdotnet) | ||
|
||
Implement credential reuse through the `UseCredential` method of `Microsoft.Extensions.Azure`: | ||
|
||
:::code language="csharp" source="../snippets/authentication/best-practices/Program.cs" id="snippet_credential_reuse_Dac" highlight="6" ::: | ||
|
||
For information on this approach, see [Authenticate using Microsoft Entra ID](/dotnet/azure/sdk/aspnetcore-guidance?tabs=api#authenticate-using-microsoft-entra-id). | ||
|
||
# [Other](#tab/other) | ||
|
||
:::code language="csharp" source="../snippets/authentication/best-practices/Program.cs" id="snippet_credential_reuse_noDac" highlight="8, 12" ::: | ||
|
||
--- | ||
|
||
## Understand the managed identity retry strategy | ||
|
||
The Azure Identity library for .NET allows you to authenticate via managed identity with `ManagedIdentityCredential`. The way in which you use `ManagedIdentityCredential` impacts the applied retry strategy. When used via: | ||
|
||
- `DefaultAzureCredential`, no retries are attempted when the initial token acquisition attempt fails or times out after a short duration. This is the least resilient option because it is optimized to "fail fast" for an efficient development inner-loop. | ||
- Any other approach, such as `ChainedTokenCredential` or `ManagedIdentityCredential` directly: | ||
- The time interval between retries starts at 0.8 seconds, and a maximum of five retries are attempted, by default. This option is optimized for resilience but introduces potentially unwanted delays in the development inner-loop. | ||
- To change any of the default retry settings, use the `Retry` property on `ManagedIdentityCredentialOptions`. For example, retry a maximum of three times, with a starting interval of 0.5 seconds: | ||
|
||
:::code language="csharp" source="../snippets/authentication/best-practices/Program.cs" id="snippet_retries" highlight="5-9" ::: | ||
|
||
For more information on customizing retry policies, see [Setting a custom retry policy](https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/samples/Configuration.md#setting-a-custom-retry-policy). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
docs/azure/sdk/snippets/authentication/best-practices/AuthBestPractices.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Azure.Identity" /> | ||
<PackageReference Include="Azure.Security.KeyVault.Secrets" /> | ||
<PackageReference Include="Azure.Storage.Blobs" /> | ||
<PackageReference Include="Microsoft.Extensions.Azure"/> | ||
</ItemGroup> | ||
|
||
</Project> |
81 changes: 81 additions & 0 deletions
81
docs/azure/sdk/snippets/authentication/best-practices/Program.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
using Azure.Identity; | ||
using Azure.Security.KeyVault.Secrets; | ||
using Azure.Storage.Blobs; | ||
using Microsoft.Extensions.Azure; | ||
|
||
var userAssignedClientId = "<user-assigned-client-id>"; | ||
var builder = WebApplication.CreateBuilder(args); | ||
|
||
#region snippet_credential_reuse_Dac | ||
builder.Services.AddAzureClients(clientBuilder => | ||
{ | ||
clientBuilder.AddSecretClient(new Uri("<key-vault-url>")); | ||
clientBuilder.AddBlobServiceClient(new Uri("<blob-storage-uri>")); | ||
|
||
clientBuilder.UseCredential(new DefaultAzureCredential()); | ||
}); | ||
#endregion snippet_credential_reuse_Dac | ||
|
||
#region snippet_credential_reuse_noDac | ||
ChainedTokenCredential credentialChain = new( | ||
new ManagedIdentityCredential( | ||
ManagedIdentityId.FromUserAssignedClientId(userAssignedClientId)), | ||
new VisualStudioCredential()); | ||
|
||
BlobServiceClient blobServiceClient = new( | ||
new Uri("<blob-storage-uri>"), | ||
credentialChain); | ||
|
||
SecretClient secretClient = new( | ||
new Uri("<key-vault-url>"), | ||
credentialChain); | ||
#endregion snippet_credential_reuse_noDac | ||
|
||
#region snippet_retries | ||
ManagedIdentityCredentialOptions miCredentialOptions = new( | ||
ManagedIdentityId.FromUserAssignedClientId(userAssignedClientId) | ||
) | ||
{ | ||
Retry = | ||
{ | ||
MaxRetries = 3, | ||
Delay = TimeSpan.FromSeconds(0.5), | ||
} | ||
}; | ||
ChainedTokenCredential tokenChain = new( | ||
new ManagedIdentityCredential(miCredentialOptions), | ||
new VisualStudioCredential() | ||
); | ||
#endregion | ||
|
||
builder.Services.AddEndpointsApiExplorer(); | ||
|
||
var app = builder.Build(); | ||
|
||
app.UseHttpsRedirection(); | ||
|
||
var summaries = new[] | ||
{ | ||
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" | ||
}; | ||
|
||
app.MapGet("/weatherforecast", () => | ||
{ | ||
var forecast = Enumerable.Range(1, 5).Select(index => | ||
new WeatherForecast | ||
( | ||
DateOnly.FromDateTime(DateTime.Now.AddDays(index)), | ||
Random.Shared.Next(-20, 55), | ||
summaries[Random.Shared.Next(summaries.Length)] | ||
)) | ||
.ToArray(); | ||
return forecast; | ||
}) | ||
.WithName("GetWeatherForecast"); | ||
|
||
app.Run(); | ||
|
||
internal record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary) | ||
{ | ||
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); | ||
} |
12 changes: 12 additions & 0 deletions
12
docs/azure/sdk/snippets/authentication/best-practices/Properties/launchSettings.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"profiles": { | ||
"AuthBestPractices": { | ||
"commandName": "Project", | ||
"launchBrowser": true, | ||
"environmentVariables": { | ||
"ASPNETCORE_ENVIRONMENT": "Development" | ||
}, | ||
"applicationUrl": "https://localhost:56902;http://localhost:56903" | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
docs/azure/sdk/snippets/authentication/best-practices/appsettings.Development.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"Logging": { | ||
"LogLevel": { | ||
"Default": "Information", | ||
"Microsoft.AspNetCore": "Warning" | ||
} | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
docs/azure/sdk/snippets/authentication/best-practices/appsettings.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"Logging": { | ||
"LogLevel": { | ||
"Default": "Information", | ||
"Microsoft.AspNetCore": "Warning" | ||
} | ||
}, | ||
"AllowedHosts": "*" | ||
} |