Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CAE capability for Managed Identity flows #4719

Draft
wants to merge 46 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
d145191
initial commit
GladwinJohnson Apr 16, 2024
ab33420
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn Apr 16, 2024
d78eb83
test fix
GladwinJohnson Apr 16, 2024
3f8c9ca
Merge branch 'gladjohn/slc_cae_pub_preview' of https://github.com/Azu…
GladwinJohnson Apr 16, 2024
88596ce
unit tests
GladwinJohnson Apr 18, 2024
cf4fe68
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn Apr 18, 2024
d72353e
fix
GladwinJohnson Apr 18, 2024
d36ba52
platformproxy
GladwinJohnson Apr 21, 2024
361b68d
key creation
GladwinJohnson Apr 22, 2024
9b12091
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn Apr 22, 2024
b80e8b7
test fix
GladwinJohnson Apr 22, 2024
3f0f3c7
dev app fix
GladwinJohnson Apr 22, 2024
620cf79
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn Apr 22, 2024
270a3d3
pr comments
GladwinJohnson Apr 25, 2024
bba95fc
Merge branch 'gladjohn/slc_cae_pub_preview' of https://github.com/Azu…
GladwinJohnson Apr 25, 2024
c6d9712
merge main to gladjohn/slc_cae_pub_preview
GladwinJohnson Apr 25, 2024
bd3db18
initial commit
GladwinJohnson Apr 16, 2024
fc87993
test fix
GladwinJohnson Apr 16, 2024
c90bba6
unit tests
GladwinJohnson Apr 18, 2024
dc387af
fix
GladwinJohnson Apr 18, 2024
512434e
platformproxy
GladwinJohnson Apr 21, 2024
15d6b7e
key creation
GladwinJohnson Apr 22, 2024
b294b15
test fix
GladwinJohnson Apr 22, 2024
467c1b4
dev app fix
GladwinJohnson Apr 22, 2024
b195572
pr comments
GladwinJohnson Apr 25, 2024
a1efed5
rebase
GladwinJohnson Apr 25, 2024
4e44131
rebase
GladwinJohnson Apr 25, 2024
fb92a24
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn Apr 30, 2024
36234e2
Merge branch 'main' into gladjohn/slc_cae_pub_preview
neha-bhargava May 1, 2024
d48eb4e
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 1, 2024
d23ce5a
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 3, 2024
f2d2a9a
pr comments
GladwinJohnson May 5, 2024
edcb66a
Test fix
GladwinJohnson May 5, 2024
8499da3
remove unused test
GladwinJohnson May 5, 2024
68c65ba
pr comments
GladwinJohnson May 5, 2024
0f4a3f0
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 6, 2024
ca1a0b6
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 9, 2024
070c34c
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 11, 2024
3121cc6
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 20, 2024
0fa96f7
OnBeforeTokenRequest on MI for testing
GladwinJohnson May 21, 2024
5ec1fda
Revert "OnBeforeTokenRequest on MI for testing"
GladwinJohnson May 22, 2024
f1d14cc
do not commit
GladwinJohnson May 22, 2024
1e816d3
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 22, 2024
4051422
build break
GladwinJohnson May 22, 2024
2eeee66
do not use IMDS when key exist
GladwinJohnson May 23, 2024
d45adbb
Merge branch 'main' into gladjohn/slc_cae_pub_preview
gladjohn May 27, 2024
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
11 changes: 7 additions & 4 deletions build/platform_and_feature_flags.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@
<DefineConstants>$(DefineConstants);NET_CORE;SUPPORTS_CONFIDENTIAL_CLIENT;SUPPORTS_CUSTOM_CACHE;SUPPORTS_BROKER;SUPPORTS_WIN32;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNet6)'" >
<DefineConstants>$(DefineConstants);SUPPORTS_SYSTEM_TEXT_JSON</DefineConstants>
<DefineConstants>$(DefineConstants);SUPPORTS_SYSTEM_TEXT_JSON</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNet6)' or '$(TargetFramework)' == '$(TargetFrameworkNetDesktop462)' or '$(TargetFramework)' == '$(TargetFrameworkNetStandard)'">
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNet6)' or '$(TargetFramework)' == '$(TargetFrameworkNetDesktop462)' or '$(TargetFramework)' == '$(TargetFrameworkNetDesktop472)' or '$(TargetFramework)' == '$(TargetFrameworkNetStandard)'">
gladjohn marked this conversation as resolved.
Show resolved Hide resolved
<DefineConstants>$(DefineConstants);SUPPORTS_OTEL;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNet6)' or '$(TargetFramework)' == '$(TargetFrameworkNetDesktop472)'">
<DefineConstants>$(DefineConstants);SUPPORTS_MTLS;</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNet6Android)'">
<DefineConstants>$(DefineConstants);ANDROID;SUPPORTS_BROKER</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNetDesktop462)'">
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNetDesktop462)' or '$(TargetFramework)' == '$(TargetFrameworkNetDesktop472)'">
<DefineConstants>$(DefineConstants);SUPPORTS_BROKER;SUPPORTS_CONFIDENTIAL_CLIENT;SUPPORTS_CUSTOM_CACHE;SUPPORTS_WIN32</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNet6Ios)'">
Expand All @@ -20,4 +23,4 @@
<PropertyGroup Condition="'$(TargetFramework)' == '$(TargetFrameworkNetStandard)'">
<DefineConstants>$(DefineConstants);NETSTANDARD;SUPPORTS_CONFIDENTIAL_CLIENT;SUPPORTS_BROKER;SUPPORTS_CUSTOM_CACHE;SUPPORTS_WIN32;</DefineConstants>
</PropertyGroup>
</Project>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.ComponentModel;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ApiConfig.Executors;
Expand Down Expand Up @@ -86,5 +87,23 @@ public T WithProofOfPossession(PoPAuthenticationConfiguration popAuthenticationC

return this as T;
}

/// <summary>
/// Sends a token request over an MTLS (Mutual TLS) connection using the provided client certificate.
/// This method is public and part of the S2S (server-to-server) token binding process,
/// allowing external clients to securely establish a mutual TLS connection.
/// </summary>
/// <remarks>
/// This method should be used when setting up a secure token exchange via MTLS.
/// It's important to ensure that the provided X509Certificate2 is valid and secure.
/// Note that only the /token request will be transmitted over this MTLS connection.
/// </remarks>
/// <param name="certificate">An X509Certificate2 object representing the client certificate for MTLS.</param>
/// <returns>An instance of the class, allowing for method chaining in a fluent interface style.</returns>
public T WithMtlsCertificate(X509Certificate2 certificate)
gladjohn marked this conversation as resolved.
Show resolved Hide resolved
{
CommonParameters.MtlsCertificate = certificate;
return this as T;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,28 @@ public AcquireTokenForManagedIdentityParameterBuilder WithForceRefresh(bool forc
return this;
}

/// <summary>
/// Adds a claims challenge to the token request. The SDK will bypass the token cache when a claims challenge is specified.. Retry the
gladjohn marked this conversation as resolved.
Show resolved Hide resolved
/// token acquisition, and use this value in the <see cref="WithClaims(string)"/> method. A claims challenge typically arises when
/// calling the protected downstream API, for example when the tenant administrator wants to revokes credentials. Apps are required
/// to look for a 401 Unauthorized response from the protected api and to parse the WWW-Authenticate response header in order to
/// extract the claims.See https://aka.ms/msal-net-claim-challenge for details. This API is not always available, depending on the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This API is not always available for managed identity flows

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would it look like when the API is not available?

/// client and that apps can monitor this by using <see cref="ManagedIdentityApplication.IsClaimsSupportedByClient"/> method
/// </summary>
/// <param name="claims">A string with one or multiple claims.</param>
/// <returns>The builder to chain .With methods.</returns>
public AcquireTokenForManagedIdentityParameterBuilder WithClaims(string claims)
{
if (string.IsNullOrEmpty(claims))
gladjohn marked this conversation as resolved.
Show resolved Hide resolved
{
throw new ArgumentNullException(nameof(claims));
}

gladjohn marked this conversation as resolved.
Show resolved Hide resolved
CommonParameters.Claims = claims;
Parameters.Claims = claims;
return this;
}

/// <inheritdoc/>
internal override Task<AuthenticationResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ApiConfig.Parameters;
using Microsoft.Identity.Client.Core;
using Microsoft.Identity.Client.Instance.Discovery;
using Microsoft.Identity.Client.Internal;
using Microsoft.Identity.Client.Internal.Requests;
using Microsoft.Identity.Client.ManagedIdentity;
using Microsoft.Identity.Client.PlatformsCommon.Interfaces;
using Microsoft.Identity.Client.Utils;

namespace Microsoft.Identity.Client.ApiConfig.Executors
Expand All @@ -20,7 +23,7 @@ internal class ManagedIdentityExecutor : AbstractExecutor, IManagedIdentityAppli
{
private readonly ManagedIdentityApplication _managedIdentityApplication;

public ManagedIdentityExecutor(IServiceBundle serviceBundle, ManagedIdentityApplication managedIdentityApplication)
public ManagedIdentityExecutor(IServiceBundle serviceBundle, ManagedIdentityApplication managedIdentityApplication)
: base(serviceBundle)
{
ClientApplicationBase.GuardMobileFrameworks();
Expand All @@ -35,19 +38,47 @@ public async Task<AuthenticationResult> ExecuteAsync(
{
var requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, cancellationToken);

if (_managedIdentityApplication.KeyMaterialManager.CryptoKeyType != CryptoKeyType.Undefined)
{
// managed identity resource
string miResource = managedIdentityParameters.Resource;

// Check if the input ends with "/.default"
bool endsWithDefault = miResource.EndsWith("/.default", StringComparison.OrdinalIgnoreCase);
gladjohn marked this conversation as resolved.
Show resolved Hide resolved

// Add "/.default" only if it doesn't end with "/.default" and doesn't contain "/.somethingelse"
gladjohn marked this conversation as resolved.
Show resolved Hide resolved
if (!endsWithDefault)
{
// Add "/.default" to the scopes
commonParameters.Scopes = new SortedSet<string>
{
managedIdentityParameters.Resource + "/.default"
};

requestContext.Logger.Verbose(() => $"User provided scope : {miResource} was updated with /.default for managed identity.");
}
}

var requestParams = await _managedIdentityApplication.CreateRequestParametersAsync(
commonParameters,
requestContext,
_managedIdentityApplication.AppTokenCacheInternal).ConfigureAwait(false);

var handler = new ManagedIdentityAuthRequest(
// MSI factory logic - decide if we need to use the legacy or the new MSI flow
RequestBase handler = null;

// May or may not be initialized, depending on the state of the Azure resource
handler = CredentialBasedMsiAuthRequest.TryCreate(
ServiceBundle,
requestParams,
managedIdentityParameters);

handler ??= new LegacyMsiAuthRequest(
ServiceBundle,
requestParams,
managedIdentityParameters);

return await handler.RunAsync(cancellationToken).ConfigureAwait(false);
}


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using Microsoft.Identity.Client.AppConfig;
using Microsoft.Identity.Client.AuthScheme;
Expand All @@ -27,6 +28,7 @@ internal class AcquireTokenCommonParameters
public IDictionary<string, string> ExtraHttpHeaders { get; set; }
public PoPAuthenticationConfiguration PopAuthenticationConfiguration { get; set; }
public Func<OnBeforeTokenRequestData, Task> OnBeforeTokenRequestHandler { get; internal set; }
public X509Certificate2 MtlsCertificate { get; internal set; }

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal class AcquireTokenForManagedIdentityParameters : IAcquireTokenParameter

public string Resource { get; set; }

public string Claims { get; set; }
gladjohn marked this conversation as resolved.
Show resolved Hide resolved

public void LogParameters(ILoggerAdapter logger)
{
if (logger.IsLoggingEnabled(LogLevel.Info))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,18 @@ public string ClientVersion

public bool RetryOnServerErrors { get; set; } = true;

#region ManagedIdentity
public ManagedIdentityId ManagedIdentityId { get; internal set; }

public bool IsManagedIdentity { get; }

public CryptoKeyType ManagedIdentityCredentialKeyType { get; internal set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to cache these here when you have the KeyMaterialManager which gets or creates them?


public X509Certificate2 ManagedIdentityClientCertificate { get; internal set; }

public bool ManagedIdentityPopSupported { get; set; } = false;
gladjohn marked this conversation as resolved.
Show resolved Hide resolved

#endregion

public bool IsConfidentialClient { get; }
public bool IsPublicClient => !IsConfidentialClient && !IsManagedIdentity;

Expand Down Expand Up @@ -205,7 +214,8 @@ public X509Certificate2 ClientCredentialCertificate
public ITokenCacheInternal UserTokenCacheInternalForTest { get; set; }
public ITokenCacheInternal AppTokenCacheInternalForTest { get; set; }

public IDeviceAuthManager DeviceAuthManagerForTest { get; set; }
public IDeviceAuthManager DeviceAuthManagerForTest { get; set; }
public IKeyMaterialManager KeyMaterialManagerForTest { get; set; }
public bool IsInstanceDiscoveryEnabled { get; internal set; } = true;
#endregion

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Net.Http;
using System.Security.Cryptography.X509Certificates;

namespace Microsoft.Identity.Client
{
/// <summary>
/// Internal factory responsible for creating HttpClient instances configured for mutual TLS (MTLS).
/// This factory is specifically intended for use within the MSAL library for secure communication with Azure AD using MTLS.
/// For more details on HttpClient instancing, see https://learn.microsoft.com/dotnet/api/system.net.http.httpclient?view=net-7.0#instancing.
/// </summary>
/// <remarks>
/// Implementations of this interface must be thread-safe.
/// It is important to reuse HttpClient instances to avoid socket exhaustion.
/// Do not create a new HttpClient for each call to <see cref="GetHttpClient(X509Certificate2)"/>.
/// If your application requires Integrated Windows Authentication, set <see cref="HttpClientHandler.UseDefaultCredentials"/> to true.
/// This interface is intended for internal use by MSAL only and is designed to support MTLS scenarios.
/// </remarks>
internal interface IMsalMtlsHttpClientFactory : IMsalHttpClientFactory
{
/// <summary>
/// Returns an HttpClient configured with a certificate for mutual TLS authentication.
/// This method enables advanced MTLS scenarios within Azure AD communications in MSAL.
/// </summary>
/// <param name="x509Certificate2">The certificate to be used for MTLS authentication.</param>
/// <returns>An HttpClient instance configured with the specified certificate.</returns>
HttpClient GetHttpClient(X509Certificate2 x509Certificate2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,47 @@ public ManagedIdentityApplicationBuilder WithTelemetryClient(params ITelemetryCl
return this;
}

/// <summary>
/// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction.
/// Allows configuration of one or more client capabilities, e.g. "llt"
/// </summary>
/// <remarks>
/// MSAL will transform these into special claims request. See https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter for
/// details on claim requests. This is an experimental API. The method signature may change in the future
/// without involving a major version upgrade.
/// For more details see https://aka.ms/msal-net-claims-request
/// </remarks>
public ManagedIdentityApplicationBuilder WithClientCapabilities(IEnumerable<string> clientCapabilities)
{
ValidateUseOfExperimentalFeature();

if (clientCapabilities != null && clientCapabilities.Any())
{
Config.ClientCapabilities = clientCapabilities;
}

return this;
}

/// <summary>
/// Microsoft Identity specific OIDC extension that allows resource challenges to be resolved without interaction.
/// Allows configuration of one or more client capabilities, e.g. "llt"
/// </summary>
/// <remarks>
/// MSAL will transform these into special claims request. See https://openid.net/specs/openid-connect-core-1_0-final.html#ClaimsParameter for
/// details on claim requests. This is an experimental API. The method signature may change in the future
/// without involving a major version upgrade.
/// For more details see https://aka.ms/msal-net-claims-request
/// </remarks>
public ManagedIdentityApplicationBuilder WithProofOfPossession()
gladjohn marked this conversation as resolved.
Show resolved Hide resolved
{
ValidateUseOfExperimentalFeature();

Config.ManagedIdentityPopSupported = true;

return this;
}

private void TelemetryClientLogMsalVersion()
{
if (Config.TelemetryClients.HasEnabledClients(TelemetryConstants.ConfigurationUpdateEventName))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Client
{
/// <summary>
/// Extensibility methods for <see cref="IManagedIdentityApplication"/>
/// </summary>
public static class ManagedIdentityApplicationExtensions
{
/// <summary>
/// Used to determine if the currently available azure resource is able to perform Proof-of-Possession.
/// </summary>
/// <returns>Boolean indicating if Proof-of-Possession is supported</returns>
public static bool IsProofOfPossessionSupportedByClient(this IManagedIdentityApplication app)
gladjohn marked this conversation as resolved.
Show resolved Hide resolved
{
if (app is ManagedIdentityApplication mia)
{
return mia.IsProofOfPossessionSupportedByClient();
}

return false;
}

/// <summary>
/// Used to determine if managed identity is able to handle claims.
/// </summary>
/// <returns>Boolean indicating if Claims is supported</returns>
public static bool IsClaimsSupportedByClient(this IManagedIdentityApplication app)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public static bool IsClaimsSupportedByClient(this IManagedIdentityApplication app)
public static bool AreClaimsSupportedByClient(this IManagedIdentityApplication app)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bgavrilMS @localden for public API

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about consistently using Is prefix? IsClientSupportingClaims?

cc: @gladjohn @bgavrilMS @pmaytak

{
if (app is ManagedIdentityApplication mia)
{
return mia.IsClaimsSupportedByClient();
}

return false;
}
}
}
Loading
Loading