From aa6c1047491d58b5772ecc6e8f6c9dc3f220fbf9 Mon Sep 17 00:00:00 2001 From: Julio Villarreal Cantu Date: Mon, 29 Jun 2026 19:38:17 -0400 Subject: [PATCH 1/3] Include ManagedIdentitySource in MI error messages and request-failure logs Managed identity authentication failures surface a host-issued 'Managed Identity Correlation ID', but the message did not indicate which managed identity source (AppService, Imds, ServiceFabric, etc.) produced it, making it ambiguous which host's telemetry to search during investigations. This change appends the detected ManagedIdentitySource to both the request-failure log line and the customer-facing correlation ID message, so the correlation ID can be traced to the correct host's telemetry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 3 +++ .../ManagedIdentity/AbstractManagedIdentity.cs | 7 +++++-- .../ManagedIdentityTests/ManagedIdentityTests.cs | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c98b4b248e..c8f00c18e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### New Features - Exposed canonical OpenTelemetry tag names per metric via `MsalMetricsCatalog.CanonicalTagsByMetric` for discoverability and validation. [#6076](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/6076) +### Changes +- Managed identity error messages and request-failure logs now include the detected `ManagedIdentitySource` (e.g., `AppService`, `Imds`, `ServiceFabric`) so the host-issued `Managed Identity Correlation ID` can be traced to the correct host's telemetry. [#TBD](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) + 4.85.0 ====== diff --git a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs index a4771ba7fc..fd91737bbd 100644 --- a/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs +++ b/src/client/Microsoft.Identity.Client/ManagedIdentity/AbstractManagedIdentity.cs @@ -188,7 +188,7 @@ protected virtual Task HandleResponseAsync( string message = GetMessageFromErrorResponse(response); - _requestContext.Logger.Error($"[Managed Identity] request failed, HttpStatusCode: {response.StatusCode} Error message: {message}"); + _requestContext.Logger.Error($"[Managed Identity] request failed, Source: {_sourceType}, HttpStatusCode: {response.StatusCode} Error message: {message}"); MsalException exception = MsalServiceExceptionFactory.CreateManagedIdentityException( MsalError.ManagedIdentityRequestFailed, @@ -281,7 +281,10 @@ private string ExtractErrorMessageFromManagedIdentityErrorResponse(ManagedIdenti if (!string.IsNullOrEmpty(managedIdentityErrorResponse.CorrelationId)) { - stringBuilder.Append($"Managed Identity Correlation ID: {managedIdentityErrorResponse.CorrelationId} Use this Correlation ID for further investigation."); + stringBuilder.Append( + $"Managed Identity Correlation ID: {managedIdentityErrorResponse.CorrelationId} " + + $"(issued by the '{_sourceType}' managed identity source; search that source's telemetry with this correlation ID). " + + $"Use this Correlation ID for further investigation."); } if (stringBuilder.Length == ManagedIdentityPrefix.Length) diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs index 849039f0e4..8f7af28473 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs @@ -559,8 +559,8 @@ await mi.AcquireTokenForManagedIdentity(resource) } [TestMethod] - [DataRow("{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID" })] - [DataRow("{\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID" })] + [DataRow("{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "AppService", "managed identity source" })] + [DataRow("{\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "AppService", "managed identity source" })] [DataRow("{\"error\":\"errorCode\",\"error_description\":\"Error message\"}", new string[] { "errorCode", "Error message" })] [DataRow("{\"error_description\":\"Error message\"}", new string[] { "Error message" })] [DataRow("{\"message\":\"Error message\"}", new string[] { "Error message" })] From b349a60a815b3efe3c42400be513c834068480e0 Mon Sep 17 00:00:00 2001 From: Julio Villarreal Cantu Date: Mon, 29 Jun 2026 19:45:31 -0400 Subject: [PATCH 2/3] Update CHANGELOG link with PR number #6101 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8f00c18e3..5934f69ae8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ - Exposed canonical OpenTelemetry tag names per metric via `MsalMetricsCatalog.CanonicalTagsByMetric` for discoverability and validation. [#6076](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/6076) ### Changes -- Managed identity error messages and request-failure logs now include the detected `ManagedIdentitySource` (e.g., `AppService`, `Imds`, `ServiceFabric`) so the host-issued `Managed Identity Correlation ID` can be traced to the correct host's telemetry. [#TBD](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet) +- Managed identity error messages and request-failure logs now include the detected `ManagedIdentitySource` (e.g., `AppService`, `Imds`, `ServiceFabric`) so the host-issued `Managed Identity Correlation ID` can be traced to the correct host's telemetry. [#6101](https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/pull/6101) 4.85.0 ====== From a25998d17e10ae92956376032a25bcecb71841c3 Mon Sep 17 00:00:00 2001 From: Julio Villarreal Cantu Date: Wed, 1 Jul 2026 12:54:24 -0400 Subject: [PATCH 3/3] Address review: source-agnostic test coverage and exact-phrase assertions - Assert the full composed phrase ("issued by the '' managed identity source") instead of the bare source name, so the check is position-independent and cannot pass if the source name merely appears in the echoed error body. - Add ManagedIdentityErrorMessageAttributesCorrelationIdToSourceAsync covering a non-AppService source (Imds) to guard the source-agnostic correlation-ID branch against regression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ManagedIdentityTests.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs index 8f7af28473..4b20bfee3e 100644 --- a/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/ManagedIdentityTests/ManagedIdentityTests.cs @@ -559,8 +559,8 @@ await mi.AcquireTokenForManagedIdentity(resource) } [TestMethod] - [DataRow("{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "AppService", "managed identity source" })] - [DataRow("{\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "AppService", "managed identity source" })] + [DataRow("{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "issued by the 'AppService' managed identity source" })] + [DataRow("{\"message\":\"Error message\",\"correlationId\":\"GUID\"}", new string[] { "Error message", "GUID", "issued by the 'AppService' managed identity source" })] [DataRow("{\"error\":\"errorCode\",\"error_description\":\"Error message\"}", new string[] { "errorCode", "Error message" })] [DataRow("{\"error_description\":\"Error message\"}", new string[] { "Error message" })] [DataRow("{\"message\":\"Error message\"}", new string[] { "Error message" })] @@ -606,6 +606,48 @@ await mi.AcquireTokenForManagedIdentity(Resource) } } + // The correlation-ID branch that attributes the host-issued correlation ID to a managed + // identity source is source-agnostic, so this guards it against regression for a + // non-AppService source (Imds) as well. It also asserts the full composed phrase rather + // than a bare source name, so the check cannot pass merely because the source name appears + // in the echoed error body. + [TestMethod] + [DataRow(ManagedIdentitySource.AppService, AppServiceEndpoint)] + [DataRow(ManagedIdentitySource.Imds, ImdsEndpoint)] + public async Task ManagedIdentityErrorMessageAttributesCorrelationIdToSourceAsync(ManagedIdentitySource managedIdentitySource, string endpoint) + { + using (new EnvVariableContext()) + using (var httpManager = new MockHttpManager(disableInternalRetries: true)) + { + SetEnvironmentVariables(managedIdentitySource, endpoint); + + var mi = ManagedIdentityApplicationBuilder.Create(ManagedIdentityId.SystemAssigned) + .WithHttpManager(httpManager) + .Build(); + + // camelCase "correlationId" maps to ManagedIdentityErrorResponse.CorrelationId, which + // triggers the correlation-ID branch that names the managed identity source. + const string errorResponse = "{\"statusCode\":500,\"message\":\"Error message\",\"correlationId\":\"some-correlation-id\"}"; + + httpManager.AddManagedIdentityMockHandler(endpoint, Resource, errorResponse, + managedIdentitySource, statusCode: HttpStatusCode.InternalServerError); + + MsalServiceException ex = await Assert.ThrowsAsync(async () => + await mi.AcquireTokenForManagedIdentity(Resource) + .ExecuteAsync().ConfigureAwait(false)).ConfigureAwait(false); + + Assert.IsNotNull(ex); + Assert.AreEqual(managedIdentitySource.ToString(), ex.AdditionalExceptionData[MsalException.ManagedIdentitySource]); + Assert.AreEqual(MsalError.ManagedIdentityRequestFailed, ex.ErrorCode); + + // Assert the exact composed phrase (position-independent-proof): only MSAL's message + // builder produces this text, so it cannot be satisfied by the echoed error body. + string expectedSourcePhrase = $"issued by the '{managedIdentitySource}' managed identity source"; + Assert.Contains(expectedSourcePhrase, ex.Message, + $"Expected the message to attribute the correlation ID to the '{managedIdentitySource}' source. Actual error message: {ex.Message}"); + } + } + [TestMethod] [DataRow("", ManagedIdentitySource.AppService, AppServiceEndpoint)] [DataRow(null, ManagedIdentitySource.AppService, AppServiceEndpoint)]