From 9391631f735e956f89fcd4e68651799fbbeb3a5b Mon Sep 17 00:00:00 2001 From: Neha Bhargava Date: Mon, 13 Jul 2026 15:44:16 -0700 Subject: [PATCH] Add OnBeforeAuthHeaderCreation / OnAfterAuthHeaderCreation hooks Add two Action? hooks to AuthorizationHeaderProviderOptions: - OnBeforeAuthHeaderCreation runs before the authorization header is created, so callers can shape the request that a request-binding protocol signs (for example a SignedHttpRequest binding the query, headers, or body via the q/h/b claims), ensuring the signature covers the finalized request. - OnAfterAuthHeaderCreation runs after the header (including Authorization) is set, just before the message is sent. Symmetric with the before hook; shares timing with the existing CustomizeHttpRequestMessage (backwards compatible). Both are copied by the copy constructor. Additive and non-breaking. Version bump and changelog are left for the post-release PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AuthorizationHeaderProviderOptions.cs | 24 +++++++++++++++++++ .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 4 ++++ .../PublicAPI/net462/PublicAPI.Unshipped.txt | 4 ++++ .../PublicAPI/net8.0/PublicAPI.Unshipped.txt | 4 ++++ .../PublicAPI/net9.0/PublicAPI.Unshipped.txt | 4 ++++ .../netstandard2.0/PublicAPI.Unshipped.txt | 4 ++++ .../netstandard2.1/PublicAPI.Unshipped.txt | 4 ++++ .../DownstreamApiTests.cs | 6 ++++- 8 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.Identity.Abstractions/DownstreamApi/AuthorizationHeaderProviderOptions.cs b/src/Microsoft.Identity.Abstractions/DownstreamApi/AuthorizationHeaderProviderOptions.cs index 8a461c4..a89303c 100644 --- a/src/Microsoft.Identity.Abstractions/DownstreamApi/AuthorizationHeaderProviderOptions.cs +++ b/src/Microsoft.Identity.Abstractions/DownstreamApi/AuthorizationHeaderProviderOptions.cs @@ -38,6 +38,8 @@ public AuthorizationHeaderProviderOptions(AuthorizationHeaderProviderOptions oth AcquireTokenOptions = other.AcquireTokenOptions.Clone(); HttpMethod = other.HttpMethod.ToString(); CustomizeHttpRequestMessage = other.CustomizeHttpRequestMessage; + OnBeforeAuthHeaderCreation = other.OnBeforeAuthHeaderCreation; + OnAfterAuthHeaderCreation = other.OnAfterAuthHeaderCreation; ProtocolScheme = other.ProtocolScheme; RequestAppToken = other.RequestAppToken; } @@ -75,6 +77,28 @@ public string HttpMethod /// public Action? CustomizeHttpRequestMessage { get; set; } + /// + /// Provides an opportunity for the caller app to customize the before + /// the authorization header is created. At this point the Authorization header is not yet present. + /// Use this to shape the request that a request-binding protocol signs — for example a SignedHttpRequest + /// (SHR) that binds the query, headers, or body (the q/h/b claims) to the token — so the + /// signature covers the finalized request. To observe or adjust the request after the header has been + /// created, use (or ). + /// + public Action? OnBeforeAuthHeaderCreation { get; set; } + + /// + /// Provides an opportunity for the caller app to customize the after + /// the authorization header (including the Authorization header) has been set, and just before the + /// message is sent — the point at which the finalized request can be read or adjusted. Provided for naming + /// symmetry with ; it shares this timing with the pre-existing + /// , which continues to be invoked for backwards compatibility. When + /// both are set they run at the same point, so callers should not depend on their relative order. Do + /// not modify material bound by a request-binding protocol (SHR q/h/b) here, since it + /// runs after signing; use for that. + /// + public Action? OnAfterAuthHeaderCreation { get; set; } + /// /// Options related to token acquisition. /// diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 7dc5c58..914edf5 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.set -> void +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.set -> void diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt index 7dc5c58..914edf5 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net462/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.set -> void +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.set -> void diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt index 7dc5c58..914edf5 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net8.0/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.set -> void +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.set -> void diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt index 7dc5c58..914edf5 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/net9.0/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.set -> void +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.set -> void diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 7dc5c58..914edf5 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.set -> void +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.set -> void diff --git a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt index 7dc5c58..914edf5 100644 --- a/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt +++ b/src/Microsoft.Identity.Abstractions/PublicAPI/netstandard2.1/PublicAPI.Unshipped.txt @@ -1 +1,5 @@ #nullable enable +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnAfterAuthHeaderCreation.set -> void +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.get -> System.Action? +Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions.OnBeforeAuthHeaderCreation.set -> void diff --git a/test/Microsoft.Identity.Abstractions.Tests/DownstreamApiTests.cs b/test/Microsoft.Identity.Abstractions.Tests/DownstreamApiTests.cs index 77383b0..fa28f89 100644 --- a/test/Microsoft.Identity.Abstractions.Tests/DownstreamApiTests.cs +++ b/test/Microsoft.Identity.Abstractions.Tests/DownstreamApiTests.cs @@ -47,6 +47,8 @@ public void CloneClonesAllProperties() }, BaseUrl = "https://apitocall.domain.com", CustomizeHttpRequestMessage = message => message.Headers.Add("x-sku", "sku-value"), + OnBeforeAuthHeaderCreation = message => message.Headers.Add("x-before", "before-value"), + OnAfterAuthHeaderCreation = message => message.Headers.Add("x-after", "after-value"), Deserializer = value => value, Serializer = input => (input != null) ? new StringContent(input.ToString()!, Encoding.UTF8 #if !NETFRAMEWORK @@ -75,6 +77,8 @@ public void CloneClonesAllProperties() Assert.Equal(downstreamApiOptions.Scopes, downstreamApiClone.Scopes!); Assert.Equal(downstreamApiOptions.BaseUrl, downstreamApiClone.BaseUrl); Assert.Equal(downstreamApiOptions.CustomizeHttpRequestMessage, downstreamApiClone.CustomizeHttpRequestMessage); + Assert.Equal(downstreamApiOptions.OnBeforeAuthHeaderCreation, downstreamApiClone.OnBeforeAuthHeaderCreation); + Assert.Equal(downstreamApiOptions.OnAfterAuthHeaderCreation, downstreamApiClone.OnAfterAuthHeaderCreation); Assert.Equal(downstreamApiOptions.Deserializer, downstreamApiClone.Deserializer); Assert.Equal(downstreamApiOptions.Serializer, downstreamApiClone.Serializer); Assert.Equal(downstreamApiOptions.HttpMethod, downstreamApiClone.HttpMethod); @@ -102,7 +106,7 @@ public void CloneClonesAllProperties() Assert.Equal(downstreamApiOptions.ExtraQueryParameters, downstreamApiClone.ExtraQueryParameters); // If this fails, think of also adding a line to test the new property - Assert.Equal(14, typeof(DownstreamApiOptions).GetProperties().Length); + Assert.Equal(16, typeof(DownstreamApiOptions).GetProperties().Length); Assert.Equal(15, typeof(AcquireTokenOptions).GetProperties().Length); DownstreamApiOptionsReadOnlyHttpMethod options = new DownstreamApiOptionsReadOnlyHttpMethod(downstreamApiOptions, HttpMethod.Delete.ToString());