diff --git a/Directory.Build.props b/Directory.Build.props
index 896f309a6..c1d4a5c6e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,7 +4,7 @@
- 4.3.0
+ 4.3.1-preview
$(MicrosoftIdentityWebVersion)
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj b/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj
index 34322a4d9..94f899947 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/Microsoft.Identity.Web.TokenAcquisition.csproj
@@ -6,6 +6,7 @@
{74737C0A-25E6-4E2C-B3AA-F7EECB7A07C7}
true
README.md
+ True
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/InternalAPI.Unshipped.txt
index 72f068746..47e6d8b58 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/InternalAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
const Microsoft.Identity.Web.Constants.ClientAssertionContainsInvalidSignature = "AADSTS7000274" -> string!
const Microsoft.Identity.Web.Constants.CertificateWasRevoked = "AADSTS7000277" -> string!
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/PublicAPI.Unshipped.txt
index 7dc5c5811..9833e6abf 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net10.0/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOf -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf?
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOfAsync -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync?
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt
index 72f068746..47e6d8b58 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/InternalAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
const Microsoft.Identity.Web.Constants.ClientAssertionContainsInvalidSignature = "AADSTS7000274" -> string!
const Microsoft.Identity.Web.Constants.CertificateWasRevoked = "AADSTS7000277" -> string!
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt
index 7dc5c5811..9833e6abf 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net462/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOf -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf?
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOfAsync -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync?
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt
index 72f068746..47e6d8b58 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/InternalAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
const Microsoft.Identity.Web.Constants.ClientAssertionContainsInvalidSignature = "AADSTS7000274" -> string!
const Microsoft.Identity.Web.Constants.CertificateWasRevoked = "AADSTS7000277" -> string!
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt
index 7dc5c5811..9833e6abf 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net472/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOf -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf?
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOfAsync -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync?
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt
index 72f068746..47e6d8b58 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/InternalAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
const Microsoft.Identity.Web.Constants.ClientAssertionContainsInvalidSignature = "AADSTS7000274" -> string!
const Microsoft.Identity.Web.Constants.CertificateWasRevoked = "AADSTS7000277" -> string!
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt
index 7dc5c5811..9833e6abf 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net8.0/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOf -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf?
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOfAsync -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync?
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt
index 72f068746..47e6d8b58 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/InternalAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
const Microsoft.Identity.Web.Constants.ClientAssertionContainsInvalidSignature = "AADSTS7000274" -> string!
const Microsoft.Identity.Web.Constants.CertificateWasRevoked = "AADSTS7000277" -> string!
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/PublicAPI.Unshipped.txt
index 7dc5c5811..9833e6abf 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/net9.0/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOf -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf?
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOfAsync -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync?
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt
index 72f068746..47e6d8b58 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/InternalAPI.Unshipped.txt
@@ -1,4 +1,6 @@
#nullable enable
const Microsoft.Identity.Web.Constants.ClientAssertionContainsInvalidSignature = "AADSTS7000274" -> string!
const Microsoft.Identity.Web.Constants.CertificateWasRevoked = "AADSTS7000277" -> string!
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOf(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static readonly Microsoft.Identity.Web.Constants.s_certificateRelatedErrorCodes -> System.Collections.Generic.HashSet!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
index 7dc5c5811..9833e6abf 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt
@@ -1 +1,7 @@
#nullable enable
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf
+Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOf -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf?
+Microsoft.Identity.Web.TokenAcquisitionExtensionOptions.OnBeforeTokenAcquisitionForOnBehalfOfAsync -> Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync?
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOf.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> void
+virtual Microsoft.Identity.Web.BeforeTokenAcquisitionForOnBehalfOfAsync.Invoke(Microsoft.Identity.Client.AcquireTokenOnBehalfOfParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? acquireTokenOptions, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
index 55436d560..941ecbe50 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
@@ -1156,10 +1156,13 @@ private void NotifyCertificateSelection(
string? tokenUsedToCallTheWebApi = GetActualToken(validatedToken);
AcquireTokenOnBehalfOfParameterBuilder? builder = null;
+ TokenAcquisitionExtensionOptions? addInOptions = null;
// Case of web APIs: we need to do an on-behalf-of flow, with the token used to call the API
if (tokenUsedToCallTheWebApi != null)
{
+ addInOptions = tokenAcquisitionExtensionOptionsMonitor?.CurrentValue;
+
if (string.IsNullOrEmpty(tokenAcquisitionOptions?.LongRunningWebApiSessionKey))
{
builder = application
@@ -1216,6 +1219,11 @@ private void NotifyCertificateSelection(
}
if (tokenAcquisitionOptions != null)
{
+ if (addInOptions != null)
+ {
+ await addInOptions.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(builder, tokenAcquisitionOptions, user!).ConfigureAwait(false);
+ }
+
AddFmiPathForSignedAssertionIfNeeded(tokenAcquisitionOptions, builder);
var dict = MergeExtraQueryParameters(mergedOptions, tokenAcquisitionOptions);
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensionOptions.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensionOptions.cs
index b2ef5e167..0d0ca0cb4 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensionOptions.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensionOptions.cs
@@ -42,6 +42,42 @@ internal void InvokeOnBeforeTokenAcquisitionForApp(AcquireTokenForClientParamete
///
public event BeforeTokenAcquisitionForTestUserAsync? OnBeforeTokenAcquisitionForTestUserAsync;
+ ///
+ /// Occurs before an asynchronous token acquisition operation for the On-Behalf-Of authentication flow is
+ /// initiated.
+ ///
+ public event BeforeTokenAcquisitionForOnBehalfOf? OnBeforeTokenAcquisitionForOnBehalfOf;
+
+ ///
+ /// Occurs before an asynchronous token acquisition operation for the On-Behalf-Of authentication flow is
+ /// initiated.
+ ///
+ public event BeforeTokenAcquisitionForOnBehalfOfAsync? OnBeforeTokenAcquisitionForOnBehalfOfAsync;
+
+ ///
+ /// Invoke the OnBeforeTokenAcquisitionForApp event.
+ ///
+ internal async Task InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(AcquireTokenOnBehalfOfParameterBuilder builder,
+ AcquireTokenOptions? acquireTokenOptions,
+ ClaimsPrincipal user)
+ {
+ // Run the async event if it is not null
+ if (OnBeforeTokenAcquisitionForOnBehalfOfAsync != null)
+ {
+ // (cannot directly await an async event because events are not tasks
+ // they are multicast delegates that invoke handlers, but don’t return values to the publisher,
+ // nor do they support awaiting natively
+ var invocationList = OnBeforeTokenAcquisitionForOnBehalfOfAsync.GetInvocationList();
+ var tasks = invocationList
+ .Cast()
+ .Select(handler => handler(builder, acquireTokenOptions, user));
+ await Task.WhenAll(tasks);
+ }
+
+ // Run the sync event if it is not null.
+ OnBeforeTokenAcquisitionForOnBehalfOf?.Invoke(builder, acquireTokenOptions, user);
+ }
+
///
/// Invoke the BeforeTokenAcquisitionForTestUser event.
///
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensions.cs b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensions.cs
index c2394f670..28035d901 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensions.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisitionExtensions.cs
@@ -31,4 +31,19 @@ namespace Microsoft.Identity.Web
/// User claims.
public delegate Task BeforeTokenAcquisitionForTestUserAsync(AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder builder, AcquireTokenOptions? acquireTokenOptions, ClaimsPrincipal user);
+ ///
+ /// Signature for token acquisition extensions that act on the request builder, for on-behalf-of flow (Async version).
+ ///
+ /// Builder
+ /// Token acquisition options for the request. Can be null.
+ /// User claims.
+ public delegate void BeforeTokenAcquisitionForOnBehalfOf(AcquireTokenOnBehalfOfParameterBuilder builder, AcquireTokenOptions? acquireTokenOptions, ClaimsPrincipal user);
+
+ ///
+ /// Signature for token acquisition extensions that act on the request builder, for on-behalf-of flow (Async version).
+ ///
+ /// Builder
+ /// Token acquisition options for the request. Can be null.
+ /// User claims.
+ public delegate Task BeforeTokenAcquisitionForOnBehalfOfAsync(AcquireTokenOnBehalfOfParameterBuilder builder, AcquireTokenOptions? acquireTokenOptions, ClaimsPrincipal user);
}
diff --git a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAddInTests.cs b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAddInTests.cs
index 625d83ac8..2148e03ce 100644
--- a/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAddInTests.cs
+++ b/tests/Microsoft.Identity.Web.Test/TokenAcquisitionAddInTests.cs
@@ -12,6 +12,9 @@
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
using System.Security.Claims;
+using Microsoft.Identity.Client.AuthScheme;
+using System.Threading;
+using System;
namespace Microsoft.Identity.Web.Tests
{
@@ -121,5 +124,106 @@ public async Task InvokeOnBeforeTokenAcquisitionForUsernamePassword_InvokesEvent
Assert.NotNull(result);
Assert.Equal(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
}
+
+ [Fact]
+ public async Task InvokeOnBeforeTokenAcquisitionForOnBehalfOf_InvokesEvent()
+ {
+ // Arrange
+ var options = new TokenAcquisitionExtensionOptions();
+ var acquireTokenOptions = new AcquireTokenOptions();
+ acquireTokenOptions.ForceRefresh = true;
+
+ //Configure mocks
+ using MockHttpClientFactory mockHttpClient = new();
+ mockHttpClient.AddMockHandler(MockHttpCreator.CreateClientCredentialTokenHandler());
+
+ var confidentialApp = ConfidentialClientApplicationBuilder
+ .Create(TestConstants.ClientId)
+ .WithAuthority(TestConstants.AuthorityCommonTenant)
+ .WithHttpClientFactory(mockHttpClient)
+ .WithInstanceDiscovery(false)
+ .WithClientSecret(TestConstants.ClientSecret)
+ .WithExperimentalFeatures(true)
+ .Build();
+
+ var userAssertion = new UserAssertion("user-assertion-token");
+ AcquireTokenOnBehalfOfParameterBuilder builder = confidentialApp
+ .AcquireTokenOnBehalfOf(new string[] { "scope" }, userAssertion);
+
+ bool eventInvoked = false;
+ bool formatResultInvoked = false;
+
+ MsalAuthenticationExtension extension = new MsalAuthenticationExtension();
+ options.OnBeforeTokenAcquisitionForOnBehalfOf += (builder, options, user) =>
+ {
+ MsalAuthenticationExtension extension = new MsalAuthenticationExtension();
+
+ // Create a test authentication operation implementing IAuthenticationOperation2
+ var authOperation = new TestAuthenticationOperation2
+ {
+ OnFormatResult = (result) =>
+ {
+ formatResultInvoked = true;
+ return Task.FromResult(result);
+ }
+ };
+ extension.AuthenticationOperation = authOperation;
+ extension.OnBeforeTokenRequestHandler = (request) =>
+ {
+ eventInvoked = true;
+ request.BodyParameters.Add("x-ms-user", user.FindFirst("user")?.Value);
+ return Task.CompletedTask;
+ };
+
+ builder.WithAuthenticationExtension(extension);
+ };
+
+ var user = new ClaimsPrincipal(
+ new CaseSensitiveClaimsIdentity(new[]
+ {
+ new Claim(ClaimConstants.Sub, "user-id"),
+ new Claim(ClaimConstants.Name, "Test User"),
+ }));
+
+ // Act
+ await options.InvokeOnBeforeTokenAcquisitionForOnBehalfOfAsync(builder, acquireTokenOptions, user);
+
+ var result = await builder.ExecuteAsync();
+
+ // Assert
+ Assert.True(eventInvoked);
+ Assert.True(formatResultInvoked);
+ Assert.NotNull(result);
+ Assert.Equal(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
+ }
+
+ // Helper class for testing IAuthenticationOperation2
+ private class TestAuthenticationOperation2 : IAuthenticationOperation2
+ {
+ public Func>? OnFormatResult { get; set; }
+
+ public int TelemetryTokenType => 0;
+
+ public string AuthorizationHeaderPrefix => "Bearer";
+
+ public string KeyId => string.Empty;
+
+ public string AccessTokenType => "Bearer";
+
+ public void FormatResult(AuthenticationResult authenticationResult) { }
+
+ public Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default)
+ {
+ if (OnFormatResult != null)
+ {
+ return OnFormatResult(authenticationResult);
+ }
+ return Task.FromResult(authenticationResult);
+ }
+
+ public IReadOnlyDictionary GetTokenRequestParams() => new Dictionary();
+
+ public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) => Task.FromResult(false);
+ }
}
}