diff --git a/sdk/core/Azure.Core/CHANGELOG.md b/sdk/core/Azure.Core/CHANGELOG.md index 90f2f9fec090..554453bc8721 100644 --- a/sdk/core/Azure.Core/CHANGELOG.md +++ b/sdk/core/Azure.Core/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs Fixed +- Fixed an issue where `BearerTokenAuthenticationPolicy` throws `ArgumentOutOfRangeException` if the `ExpiresOn` property of the token is the default value. ([#47040](https://github.com/Azure/azure-sdk-for-net/pull/47040)) + ### Other Changes - Use `BinaryData.Empty` for `PipelineResponse.Content` when HTTP message has no content. diff --git a/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs b/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs index 83a8f5aca873..c13b7bc7666f 100644 --- a/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs +++ b/sdk/core/Azure.Core/src/Pipeline/BearerTokenAuthenticationPolicy.cs @@ -395,7 +395,14 @@ private async ValueTask SetResultOnTcsFromCredentialAsync(TokenRequestContext co ? await _credential.GetTokenAsync(context, cancellationToken).ConfigureAwait(false) : _credential.GetToken(context, cancellationToken); - targetTcs.SetResult(new AuthHeaderValueInfo("Bearer " + token.Token, token.ExpiresOn, token.RefreshOn.HasValue ? token.RefreshOn.Value : token.ExpiresOn - _tokenRefreshOffset)); + DateTimeOffset refreshOn = token.RefreshOn.HasValue switch + { + true => token.RefreshOn.Value, + false when _tokenRefreshOffset.Ticks > token.ExpiresOn.Ticks => token.ExpiresOn, + _ => token.ExpiresOn - _tokenRefreshOffset + }; + + targetTcs.SetResult(new AuthHeaderValueInfo("Bearer " + token.Token, token.ExpiresOn, refreshOn)); } internal readonly struct AuthHeaderValueInfo diff --git a/sdk/core/Azure.Core/tests/BearerTokenAuthenticationPolicyTests.cs b/sdk/core/Azure.Core/tests/BearerTokenAuthenticationPolicyTests.cs index 7597391db2ec..e132d3b3a576 100644 --- a/sdk/core/Azure.Core/tests/BearerTokenAuthenticationPolicyTests.cs +++ b/sdk/core/Azure.Core/tests/BearerTokenAuthenticationPolicyTests.cs @@ -57,6 +57,30 @@ public async Task BearerTokenAuthenticationPolicy_RequestsTokenEveryRequest() Assert.AreEqual("Bearer token2", auth2Value); } + [Test] + public async Task BearerTokenAuthenticationPolicy_RequestsTokenEveryRequest_InvalidExpiresOn() + { + var accessTokens = new Queue(); + accessTokens.Enqueue(new AccessToken("token1", default)); + accessTokens.Enqueue(new AccessToken("token2", default)); + + var credential = new TokenCredentialStub( + (r, c) => r.Scopes.SequenceEqual(new[] { "scope1", "scope2" }) ? accessTokens.Dequeue() : default, + IsAsync); + + var policy = new BearerTokenAuthenticationPolicy(credential, new[] { "scope1", "scope2" }); + MockTransport transport = CreateMockTransport(new MockResponse(200), new MockResponse(200)); + + await SendGetRequest(transport, policy, uri: new Uri("https://example.com")); + await SendGetRequest(transport, policy, uri: new Uri("https://example.com")); + + Assert.True(transport.Requests[0].Headers.TryGetValue("Authorization", out string auth1Value)); + Assert.True(transport.Requests[1].Headers.TryGetValue("Authorization", out string auth2Value)); + + Assert.AreEqual("Bearer token1", auth1Value); + Assert.AreEqual("Bearer token2", auth2Value); + } + [Test] public async Task BearerTokenAuthenticationPolicy_CachesHeaderValue() {