Skip to content

Commit 01bca84

Browse files
trwalketrwalke
andauthored
Ensure correlationId is added to request_timeout exception when available (#5364)
* Ensure correlationId is added to retry exception when available * Fixing test * Add additional test * Update httpManager --------- Co-authored-by: trwalke <[email protected]>
1 parent fa932de commit 01bca84

File tree

3 files changed

+114
-3
lines changed

3 files changed

+114
-3
lines changed

src/client/Microsoft.Identity.Client/Http/HttpManager.cs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using System.Threading.Tasks;
1515
using Microsoft.Identity.Client.Core;
1616
using Microsoft.Identity.Client.Http.Retry;
17+
using Microsoft.Identity.Client.OAuth2;
1718

1819
namespace Microsoft.Identity.Client.Http
1920
{
@@ -134,10 +135,30 @@ public async Task<HttpResponse> SendRequestAsync(
134135
logger.Warning("Request retry failed.");
135136
if (timeoutException != null)
136137
{
138+
//If the correlation id is available, include it in the exception message
139+
string msg = MsalErrorMessage.RequestTimeOut;
140+
141+
if (headers != null && headers.Count > 0)
142+
{
143+
var correlationId = headers[OAuth2Header.CorrelationId];
144+
string correlationIdMsg = headers.ContainsKey(OAuth2Header.CorrelationId) ?
145+
$" CorrelationId: {correlationId}" :
146+
string.Empty;
147+
148+
var ex = new MsalServiceException(
149+
MsalError.RequestTimeout,
150+
msg + correlationIdMsg,
151+
timeoutException);
152+
153+
ex.CorrelationId = correlationId;
154+
155+
throw ex;
156+
}
157+
137158
throw new MsalServiceException(
138-
MsalError.RequestTimeout,
139-
"Request to the endpoint timed out.",
140-
timeoutException);
159+
MsalError.RequestTimeout,
160+
msg,
161+
timeoutException);
141162
}
142163

143164
if (doNotThrow)

src/client/Microsoft.Identity.Client/MsalErrorMessage.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,5 +438,6 @@ public static string InvalidTokenProviderResponseValue(string invalidValueName)
438438
public const string MtlsNonTenantedAuthorityNotAllowedMessage = "mTLS authentication requires a tenanted authority. Using 'common', 'organizations', or similar non-tenanted authorities is not allowed. Please provide an authority with a specific tenant ID (e.g., 'https://login.microsoftonline.com/{tenantId}'). See https://aka.ms/msal-net-pop for details.";
439439
public const string RegionRequiredForMtlsPopMessage = "Regional auto-detect failed. mTLS Proof-of-Possession requires a region to be specified, as there is no global endpoint for mTLS. See https://aka.ms/msal-net-pop for details.";
440440
public const string ForceRefreshAndTokenHasNotCompatible = "Cannot specify ForceRefresh and AccessTokenSha256ToRefresh in the same request.";
441+
public const string RequestTimeOut = "Request to the endpoint timed out.";
441442
}
442443
}

tests/Microsoft.Identity.Test.Unit/CoreTests/HttpTests/HttpManagerTests.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
using Microsoft.Identity.Client;
1414
using Microsoft.Identity.Client.Core;
1515
using Microsoft.Identity.Client.Http.Retry;
16+
using Microsoft.Identity.Client.OAuth2;
1617
using Microsoft.Identity.Test.Common;
1718
using Microsoft.Identity.Test.Common.Core.Helpers;
1819
using Microsoft.Identity.Test.Common.Core.Mocks;
@@ -536,6 +537,94 @@ public async Task TestSendPostWithRetryOnTimeoutFailureAsync()
536537
}
537538
}
538539

540+
[TestMethod]
541+
[DataRow(true)]
542+
[DataRow(false)]
543+
public async Task TestCorrelationIdWithRetryOnTimeoutFailureAsync(bool addCorrelationId)
544+
{
545+
using (var httpManager = new MockHttpManager())
546+
{
547+
// Simulate permanent errors (to trigger the maximum number of retries)
548+
const int Num500Errors = 1 + TestDefaultRetryPolicy.DefaultStsMaxRetries; // initial request + maximum number of retries
549+
for (int i = 0; i < Num500Errors; i++)
550+
{
551+
httpManager.AddRequestTimeoutResponseMessageMockHandler(HttpMethod.Post);
552+
}
553+
554+
Guid correlationId = Guid.NewGuid();
555+
var headers = new Dictionary<string, string>();
556+
557+
if (addCorrelationId)
558+
{
559+
headers.Add(OAuth2Header.CorrelationId, correlationId.ToString());
560+
}
561+
562+
var exc = await AssertException.TaskThrowsAsync<MsalServiceException>(() =>
563+
httpManager.SendRequestAsync(
564+
new Uri(TestConstants.AuthorityHomeTenant + "oauth2/token"),
565+
headers: headers,
566+
body: new FormUrlEncodedContent(new Dictionary<string, string>()),
567+
method: HttpMethod.Post,
568+
logger: Substitute.For<ILoggerAdapter>(),
569+
doNotThrow: false,
570+
mtlsCertificate: null,
571+
validateServerCert: null,
572+
cancellationToken: default,
573+
retryPolicy: _stsRetryPolicy))
574+
.ConfigureAwait(false);
575+
576+
Assert.AreEqual(MsalError.RequestTimeout, exc.ErrorCode);
577+
578+
if (addCorrelationId)
579+
{
580+
Assert.AreEqual($"Request to the endpoint timed out. CorrelationId: {correlationId.ToString()}", exc.Message);
581+
Assert.AreEqual(correlationId.ToString(), exc.CorrelationId);
582+
}
583+
else
584+
{
585+
Assert.AreEqual("Request to the endpoint timed out.", exc.Message);
586+
}
587+
}
588+
}
589+
590+
[TestMethod]
591+
public async Task TestWithCorrelationId_RetryOnTimeoutFailureAsync()
592+
{
593+
// Arrange
594+
using (var httpManager = new MockHttpManager())
595+
{
596+
httpManager.AddInstanceDiscoveryMockHandler();
597+
598+
// Simulate permanent errors (to trigger the maximum number of retries)
599+
const int Num500Errors = 1 + TestDefaultRetryPolicy.DefaultStsMaxRetries; // initial request + maximum number of retries
600+
for (int i = 0; i < Num500Errors; i++)
601+
{
602+
httpManager.AddRequestTimeoutResponseMessageMockHandler(HttpMethod.Post);
603+
}
604+
Guid correlationId = Guid.NewGuid();
605+
606+
var app = ConfidentialClientApplicationBuilder
607+
.Create(TestConstants.ClientId)
608+
.WithAuthority(TestConstants.AuthorityTestTenant)
609+
.WithHttpManager(httpManager)
610+
.WithClientSecret(TestConstants.ClientSecret)
611+
.Build();
612+
613+
var userAssertion = new UserAssertion(TestConstants.DefaultAccessToken);
614+
615+
// Act
616+
var exc = await AssertException.TaskThrowsAsync<MsalServiceException>(() =>
617+
app.AcquireTokenForClient(TestConstants.s_scope)
618+
.WithCorrelationId(correlationId)
619+
.ExecuteAsync())
620+
.ConfigureAwait(false);
621+
622+
// Assert
623+
Assert.AreEqual($"Request to the endpoint timed out. CorrelationId: {correlationId.ToString()}", exc.Message);
624+
Assert.AreEqual(correlationId.ToString(), exc.CorrelationId);
625+
}
626+
}
627+
539628
private class CapturingHandler : HttpMessageHandler
540629
{
541630
public HttpRequestMessage CapturedRequest { get; private set; }

0 commit comments

Comments
 (0)