Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
d558326
Initial plan
Copilot Jul 14, 2025
1e0698b
Initial analysis and plan for OidcIdpSignedAssertionLoader IConfigura…
Copilot Jul 14, 2025
67ce505
Fix OidcIdpSignedAssertionLoader to use IServiceProvider instead of I…
Copilot Jul 14, 2025
62ef492
Implement high-performance logging and unit tests for OidcIdpSignedAs…
Copilot Jul 14, 2025
7ec62f0
Address PR feedback: improve error message, remove unnecessary files …
Copilot Jul 14, 2025
84f632b
Restore InternalsVisibleTo.cs file for Microsoft.Identity.Web.OidcFIC…
Copilot Jul 14, 2025
59a5f52
Remove accidentally added dotnet-install.sh file
Copilot Jul 15, 2025
e505000
Remove DynamicProxyGenAssembly2 InternalsVisibleTo attribute from Oid…
Copilot Jul 15, 2025
b36c926
Remove unnecessary InternalsVisibleTo attributes from OidcFIC project
Copilot Jul 15, 2025
ec19f7b
Fixing the logger initialization
jmprieur Jul 15, 2025
7601075
Implement attribute-based LoggerMessage for OidcIdpSignedAssertionLoader
Copilot Jul 15, 2025
ff8419c
Merge branch 'master' into copilot/fix-3411
jmprieur Jul 15, 2025
92bd1ad
Merge branch 'master' into copilot/fix-3411
jmprieur Jul 15, 2025
24bd420
Simplify logging to use only LoggerMessageAttribute approach and remo…
Copilot Jul 17, 2025
ceb2c7e
Merge branch 'master' into copilot/fix-3411
jmprieur Jul 18, 2025
72f0668
First cut
jmprieur Jul 22, 2025
2aaad4d
merge from master
jmprieur Jul 22, 2025
03f45ac
WP
jmprieur Jul 22, 2025
a3262ed
Flow attempt
jmprieur Jul 23, 2025
130827b
Prototype
jmprieur Jul 24, 2025
31b2093
Clean-up
jmprieur Jul 24, 2025
e52bf58
Improving the tests
jmprieur Jul 24, 2025
38684d2
Change of approach
jmprieur Jul 25, 2025
fa5d7b0
Update with cloned options
jmprieur Jul 25, 2025
69f24d0
Fixing the tests
jmprieur Jul 25, 2025
7a43c8a
Simplifying
jmprieur Jul 25, 2025
5161ed0
Simplifying even more
jmprieur Jul 25, 2025
2fe77a7
Adding a test for the cached user
jmprieur Jul 25, 2025
3276501
Merge from master
jmprieur Jul 25, 2025
72b1bc4
Update tests/E2E Tests/AgentApplications/TokenAcquirerFactorySingleto…
jmprieur Jul 26, 2025
81889ff
Update src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
jmprieur Jul 26, 2025
96e6a45
Update src/Microsoft.Identity.Web.TokenAcquisition/TokenAcquisition.cs
jmprieur Jul 26, 2025
a0cdfec
Addreessing PR feedback
jmprieur Jul 26, 2025
781c0d7
Fixing typo
jmprieur Jul 26, 2025
337677d
Updating tests
jmprieur Jul 26, 2025
84aa4ea
work in progress
jmprieur Jul 29, 2025
e7b12a7
Updated code for AUI
jmprieur Jul 29, 2025
d3ca549
Cleaning-up the tests.
jmprieur Jul 29, 2025
c937f4f
Adressing feedback
jmprieur Jul 29, 2025
efe6fe7
removing unused constant
jmprieur Jul 29, 2025
5d9a120
Accelerating the tests, and avoid running them in GitHub actions
jmprieur Jul 29, 2025
dad6250
Disabling tests when run in GitHub actionss
jmprieur Jul 29, 2025
c49ad28
Disabling Agent E2E tests in GitHub actions
jmprieur Jul 29, 2025
d97d5d0
Removing the agents tests in GitHub Actions
jmprieur Jul 29, 2025
5379a51
Update src/Microsoft.Identity.Web.AgentIdentities/AgentIdentitiesExte…
jmprieur Jul 30, 2025
dc3ea2b
Renaming (PR review)
jmprieur Jul 30, 2025
450e7b7
Merge branch 'origin/jmprieur/aui2' of https://github.com/AzureAD/mic…
jmprieur Jul 30, 2025
0f669d0
Adrressing PR feedback
jmprieur Jul 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/dotnetcore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ jobs:
run: msbuild Microsoft.Identity.Web.sln -r -t:build -verbosity:m -property:Configuration=Release

- name: Test with .NET 8.0.x
run: dotnet test --no-restore --no-build Microsoft.Identity.Web.sln -f net8.0 -v m -p:FROM_GITHUB_ACTION=true --configuration Release --collect "Xplat Code Coverage" --filter "(FullyQualifiedName!~Microsoft.Identity.Web.Test.Integration)&(FullyQualifiedName!~WebAppUiTests)&(FullyQualifiedName!~IntegrationTests)&(FullyQualifiedName!~TokenAcquirerTests)"
run: dotnet test --no-restore --no-build Microsoft.Identity.Web.sln -f net8.0 -v m -p:FROM_GITHUB_ACTION=true --configuration Release --collect "Xplat Code Coverage" --filter "(FullyQualifiedName!~Microsoft.Identity.Web.Test.Integration)&(FullyQualifiedName!~WebAppUiTests)&(FullyQualifiedName!~IntegrationTests)&(FullyQualifiedName!~TokenAcquirerTests)&(FullyQualifiedName!~AgentApplicationsTests)"

- name: Test with .NET 9.0.x
run: dotnet test --no-restore --no-build Microsoft.Identity.Web.sln -f net9.0 -v m -p:FROM_GITHUB_ACTION=true --configuration Release --collect "Xplat Code Coverage" --filter "(FullyQualifiedName!~Microsoft.Identity.Web.Test.Integration)&(FullyQualifiedName!~WebAppUiTests)&(FullyQualifiedName!~IntegrationTests)&(FullyQualifiedName!~TokenAcquirerTests)"
run: dotnet test --no-restore --no-build Microsoft.Identity.Web.sln -f net9.0 -v m -p:FROM_GITHUB_ACTION=true --configuration Release --collect "Xplat Code Coverage" --filter "(FullyQualifiedName!~Microsoft.Identity.Web.Test.Integration)&(FullyQualifiedName!~WebAppUiTests)&(FullyQualifiedName!~IntegrationTests)&(FullyQualifiedName!~TokenAcquirerTests)&(FullyQualifiedName!~AgentApplicationsTests)"

- name: Test with .NET 6.0.x
run: dotnet test Microsoft.Identity.Web.sln -f net6.0 -v m -p:FROM_GITHUB_ACTION=true --configuration Release --filter "(FullyQualifiedName!~Microsoft.Identity.Web.Test.Integration)&(FullyQualifiedName!~WebAppUiTests)&(FullyQualifiedName!~IntegrationTests)&(FullyQualifiedName!~TokenAcquirerTests)&(FullyQualifiedName!~AgentApplicationsTests)"
Expand Down
4 changes: 2 additions & 2 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<!--This should be passed from the VSTS build-->
<!-- This needs to be greater than or equal to the validation baseline version -->
<MicrosoftIdentityWebVersion Condition="'$(MicrosoftIdentityWebVersion)' == ''">3.10.1</MicrosoftIdentityWebVersion>
<MicrosoftIdentityWebVersion Condition="'$(MicrosoftIdentityWebVersion)' == ''">3.12.0</MicrosoftIdentityWebVersion>
<!--This will generate AssemblyVersion, AssemblyFileVersion and AssemblyInformationVersion-->
<Version>$(MicrosoftIdentityWebVersion)</Version>

Expand Down Expand Up @@ -79,7 +79,7 @@

<PropertyGroup Label="Common dependency versions">
<MicrosoftIdentityModelVersion Condition="'$(MicrosoftIdentityModelVersion)' == ''">8.12.1</MicrosoftIdentityModelVersion>
<MicrosoftIdentityClientVersion Condition="'$(MicrosoftIdentityClientVersion)' == ''">4.73.1</MicrosoftIdentityClientVersion>
<MicrosoftIdentityClientVersion Condition="'$(MicrosoftIdentityClientVersion)' == ''">4.74.1</MicrosoftIdentityClientVersion>
<FxCopAnalyzersVersion>3.3.0</FxCopAnalyzersVersion>
<SystemTextEncodingsWebVersion>4.7.2</SystemTextEncodingsWebVersion>
<AzureSecurityKeyVaultSecretsVersion>4.6.0</AzureSecurityKeyVaultSecretsVersion>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Web.AgentIdentities;

namespace Microsoft.Identity.Web
{
Expand All @@ -28,10 +25,15 @@ public static IServiceCollection AddAgentIdentities(this IServiceCollection serv
// Register the OidcFic services for agent applications to work.
services.AddOidcFic();

// Register a callback to process the agent user identity before acquiring a token.
services.Configure<TokenAcquisitionExtensionOptions>(options =>
{
options.OnBeforeTokenAcquisitionForTestUserAsync += AgentUserIdentityMsalAddIn.OnBeforeUserFicForAgentUserIdentityAsync;
});

return services;
}


/// <summary>
/// Updates the options to acquire a token for the agent identity.
/// </summary>
Expand All @@ -51,24 +53,50 @@ public static AuthorizationHeaderProviderOptions WithAgentIdentity(this Authoriz
return options;
}

// TODO:make public?
private static AcquireTokenOptions ForAgentIdentity(this AcquireTokenOptions options, string agentApplicationId)
/// <summary>
/// Updates the options to acquire a token for the agent user identity.
/// </summary>
/// <param name="options">Authorization header provider options.</param>
/// <param name="agentApplicationId">The agent identity GUID.</param>
/// <param name="username">UPN of the user.</param>
/// <returns>The updated authorization header provider options (in place. not a clone of the options).</returns>
public static AuthorizationHeaderProviderOptions WithAgentUserIdentity(this AuthorizationHeaderProviderOptions options, string agentApplicationId, string username)
{
options ??= new AuthorizationHeaderProviderOptions();
options.AcquireTokenOptions ??= new AcquireTokenOptions();
options.AcquireTokenOptions.ExtraParameters ??= new Dictionary<string, object>();

// Set the agent application options
options.AcquireTokenOptions.ExtraParameters[Constants.MicrosoftIdentityOptionsParameter] = new MicrosoftEntraApplicationOptions
{
ClientId = agentApplicationId, // Agent identity Client ID.
};

// Set the username and agent identity parameters
options.AcquireTokenOptions.ExtraParameters[Constants.UsernameKey] = username;
options.AcquireTokenOptions.ExtraParameters[Constants.AgentIdentityKey] = agentApplicationId;

return options;
}

// TODO:would it make sense to have it public?
internal static AcquireTokenOptions ForAgentIdentity(this AcquireTokenOptions options, string agentApplicationId)
{
options.ExtraParameters ??= new Dictionary<string, object>();

// Until it makes it way through Abstractions
options.ExtraParameters["fmiPathForClientAssertion"] = agentApplicationId;
options.ExtraParameters[Constants.FmiPathForClientAssertion] = agentApplicationId;

// TODO: do we want to expose a mechanism to override the MicrosoftIdentityOptions instead of leveraging
// the default configuration section / named options?.
options.ExtraParameters["MicrosoftIdentityOptions"] = new MicrosoftEntraApplicationOptions
options.ExtraParameters[Constants.MicrosoftIdentityOptionsParameter] = new MicrosoftEntraApplicationOptions
{
ClientId = agentApplicationId, // Agent identity Client ID.
ClientCredentials = [ new CredentialDescription() {
SourceType = CredentialSource.CustomSignedAssertion,
CustomSignedAssertionProviderName = "OidcIdpSignedAssertion",
CustomSignedAssertionProviderData = new Dictionary<string, object> {
{ "ConfigurationSection", "AzureAd" }, // Use the default configuration section name
{ "ConfigurationSection", "AzureAd" }, // Use the default configuration section name
{ "RequiresSignedAssertionFmiPath", true }, // The OidcIdpSignedAssertionProvider will require the fmiPath to be provided in the assertionRequestOptions.
}
}]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.Identity.Abstractions;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensibility;

namespace Microsoft.Identity.Web.AgentIdentities
{
internal static class AgentUserIdentityMsalAddIn
{
internal static Task OnBeforeUserFicForAgentUserIdentityAsync(
AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder builder,
AcquireTokenOptions? options,
ClaimsPrincipal user)
{
if (options == null || options.ExtraParameters == null)
{
return Task.CompletedTask;
}
IServiceProvider serviceProvider = (IServiceProvider)options.ExtraParameters![Constants.ExtensionOptionsServiceProviderKey];
options.ExtraParameters.TryGetValue(Constants.AgentIdentityKey, out object? agentIdentityObject);
options.ExtraParameters.TryGetValue(Constants.UsernameKey, out object? usernameObject);
if (agentIdentityObject is string agentIdentity && usernameObject is string username)
{
// Register the MSAL extension that will modify the token request just in time.
MsalAuthenticationExtension extension = new()
{
OnBeforeTokenRequestHandler = async (request) =>
{
// Get the services from the service provider.
ITokenAcquirerFactory tokenAcquirerFactory = serviceProvider.GetRequiredService<ITokenAcquirerFactory>();
IAuthenticationSchemeInformationProvider authenticationSchemeInformationProvider =
serviceProvider.GetRequiredService<IAuthenticationSchemeInformationProvider>();
IOptionsMonitor<MicrosoftIdentityApplicationOptions> optionsMonitor =
serviceProvider.GetRequiredService<IOptionsMonitor<MicrosoftIdentityApplicationOptions>>();

// Get the FIC token for the agent application.
string authenticationScheme = authenticationSchemeInformationProvider.GetEffectiveAuthenticationScheme(options.AuthenticationOptionsName);
ITokenAcquirer tokenAcquirer = tokenAcquirerFactory.GetTokenAcquirer(authenticationScheme);
ITokenAcquirer agentApplicationTokenAcquirer = tokenAcquirerFactory.GetTokenAcquirer();
AcquireTokenResult aaFic = await agentApplicationTokenAcquirer.GetFicTokenAsync(new() { FmiPath = agentIdentity }); // Uses the regular client credentials
string? clientAssertion = aaFic.AccessToken;

// Get the FIC token for the agent identity.
MicrosoftIdentityApplicationOptions microsoftIdentityApplicationOptions = optionsMonitor.Get(authenticationScheme);
ITokenAcquirer agentIdentityTokenAcquirer = tokenAcquirerFactory.GetTokenAcquirer(new MicrosoftIdentityApplicationOptions
{
ClientId = agentIdentity,
Instance = microsoftIdentityApplicationOptions.Instance,
Authority = microsoftIdentityApplicationOptions.Authority,
TenantId = microsoftIdentityApplicationOptions.TenantId
});
AcquireTokenResult aidFic = await agentIdentityTokenAcquirer.GetFicTokenAsync(clientAssertion: clientAssertion); // Uses the agent identity
string? userFicAssertion = aidFic.AccessToken;

// Already in the request:
// - client_id = agentIdentity;

// User FIC parameters
request.BodyParameters["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
request.BodyParameters["client_assertion"] = clientAssertion;
request.BodyParameters["username"] = username;
request.BodyParameters["user_federated_identity_credential"] = userFicAssertion;
request.BodyParameters["grant_type"] = "user_fic";
request.BodyParameters.Remove("password");

if (request.BodyParameters.TryGetValue("client_secret", out var secret)
&& secret.Equals("default", StringComparison.OrdinalIgnoreCase))
{
request.BodyParameters.Remove("client_secret");
}

// For the moment
request.RequestUri = new Uri(request.RequestUri + "?slice=first");
}
};
builder.WithAuthenticationExtension(extension);
}
return Task.CompletedTask;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@

<!-- The package is new in 3.10.0.-->
<PackageValidationBaselineVersion>3.10.0</PackageValidationBaselineVersion>
<EnablePackageValidation>false</EnablePackageValidation>

<EnablePackageValidation>true</EnablePackageValidation>
</PropertyGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
#nullable enable
Microsoft.Identity.Web.AgentIdentities.AgentUserIdentityMsalAddIn
Microsoft.Identity.Web.AgentIdentities.AgentUserIdentityMsalAddIn.AgentUserIdentityMsalAddIn() -> void
static Microsoft.Identity.Web.AgentIdentities.AgentUserIdentityMsalAddIn.OnBeforeUserFicForAgentUserIdentityAsync(Microsoft.Identity.Client.AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder! builder, Microsoft.Identity.Abstractions.AcquireTokenOptions? options, System.Security.Claims.ClaimsPrincipal! user) -> System.Threading.Tasks.Task!
static Microsoft.Identity.Web.AgentIdentityExtension.ForAgentIdentity(this Microsoft.Identity.Abstractions.AcquireTokenOptions! options, string! agentApplicationId) -> Microsoft.Identity.Abstractions.AcquireTokenOptions!
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
static Microsoft.Identity.Web.AgentIdentityExtension.WithAgentUserIdentity(this Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions! options, string! agentApplicationId, string! username) -> Microsoft.Identity.Abstractions.AuthorizationHeaderProviderOptions!
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ public static void SignedAssertionProviderFailed(
}



public async Task LoadIfNeededAsync(CredentialDescription credentialDescription, CredentialSourceLoaderParameters? parameters = null)
{
OidcIdpSignedAssertionProvider? signedAssertion = credentialDescription.CachedValue as OidcIdpSignedAssertionProvider;
Expand Down
9 changes: 9 additions & 0 deletions src/Microsoft.Identity.Web.TokenAcquisition/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ public static class Constants
internal const string CiamAuthoritySuffix = ".ciamlogin.com";
internal const string TestSlice = "dc";
internal const string ExtensionOptionsServiceProviderKey = "ID_WEB_INTERNAL_SERVICE_PROVIDER";
internal const string AgentIdentityKey = "IDWEB_AGENT_IDENTITY";
internal const string UsernameKey = "IDWEB_USERNAME";
internal const string FmiPathForClientAssertion = "IDWEB_FMI_PATH_FOR_CLIENT_ASSERTION";
internal const string MicrosoftIdentityOptionsParameter = "IDWEB_FMI_MICROSOFT_IDENTITY_OPTIONS";

/// <summary>
/// Key for client assertion in token acquisition options.
/// </summary>
internal const string ClientAssertion = "IDWEB_CLIENT_ASSERTION";

// Blazor challenge URI
internal const string BlazorChallengeUri = "MicrosoftIdentity/Account/Challenge?redirectUri=";
Expand Down
Loading
Loading