Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Identity.Client.ApiConfig.Executors;
using Microsoft.Identity.Client.ApiConfig.Parameters;
using Microsoft.Identity.Client.TelemetryCore.Internal.Events;

namespace Microsoft.Identity.Client
{
/// <summary>
/// Parameter builder for the <see cref="IByUserFederatedIdentityCredential.AcquireTokenByUserFederatedIdentityCredential(IEnumerable{string}, string, Func{AssertionRequestOptions, Task{string}})"/>
/// operation.
/// </summary>
#if !SUPPORTS_CONFIDENTIAL_CLIENT
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] // hide confidential client on mobile
#endif
public sealed class AcquireTokenByUserFederatedIdentityCredentialParameterBuilder :
AbstractConfidentialClientAcquireTokenParameterBuilder<AcquireTokenByUserFederatedIdentityCredentialParameterBuilder>
{
private AcquireTokenByUserFederatedIdentityCredentialParameters Parameters { get; } = new AcquireTokenByUserFederatedIdentityCredentialParameters();

internal AcquireTokenByUserFederatedIdentityCredentialParameterBuilder(
IConfidentialClientApplicationExecutor confidentialClientApplicationExecutor,
string username,
Func<AssertionRequestOptions, Task<string>> assertionProvider)
Comment thread
neha-bhargava marked this conversation as resolved.
Outdated
: base(confidentialClientApplicationExecutor)
{
Parameters.Username = username;
Parameters.AssertionProvider = assertionProvider;
}

internal static AcquireTokenByUserFederatedIdentityCredentialParameterBuilder Create(
IConfidentialClientApplicationExecutor confidentialClientApplicationExecutor,
IEnumerable<string> scopes,
string username,
Func<AssertionRequestOptions, Task<string>> assertionProvider)
{
if (string.IsNullOrEmpty(username))
{
throw new ArgumentNullException(nameof(username));
}

if (assertionProvider == null)
{
throw new ArgumentNullException(nameof(assertionProvider));
}

return new AcquireTokenByUserFederatedIdentityCredentialParameterBuilder(
confidentialClientApplicationExecutor, username, assertionProvider)
.WithScopes(scopes);
}

/// <summary>
/// Forces MSAL to refresh the token from the identity provider even if a cached token is available.
/// </summary>
/// <param name="forceRefresh"><c>true</c> to bypass the cache; otherwise <c>false</c>. Default is <c>false</c>.</param>
/// <returns>The builder to chain the .With methods</returns>
public AcquireTokenByUserFederatedIdentityCredentialParameterBuilder WithForceRefresh(bool forceRefresh)
{
Parameters.ForceRefresh = forceRefresh;
return this;
}

/// <summary>
/// Applicable to first-party applications only, this method also allows to specify
/// if the <see href="https://datatracker.ietf.org/doc/html/rfc7517#section-4.7">x5c claim</see> should be sent to Azure AD.
/// Sending the x5c enables application developers to achieve easy certificate roll-over in Azure AD:
/// this method will send the certificate chain to Azure AD along with the token request,
/// so that Azure AD can use it to validate the subject name based on a trusted issuer policy.
/// This saves the application admin from the need to explicitly manage the certificate rollover
/// (either via portal or PowerShell/CLI operation). For details see https://aka.ms/msal-net-sni
/// </summary>
/// <param name="withSendX5C"><c>true</c> if the x5c should be sent. Otherwise <c>false</c>.
/// The default is <c>false</c></param>
/// <returns>The builder to chain the .With methods</returns>
public AcquireTokenByUserFederatedIdentityCredentialParameterBuilder WithSendX5C(bool withSendX5C)
{
Parameters.SendX5C = withSendX5C;
return this;
}

/// <inheritdoc/>
internal override Task<AuthenticationResult> ExecuteInternalAsync(CancellationToken cancellationToken)
{
return ConfidentialClientApplicationExecutor.ExecuteAsync(CommonParameters, Parameters, cancellationToken);
}

/// <inheritdoc/>
internal override ApiEvent.ApiIds CalculateApiEventId()
{
return ApiEvent.ApiIds.AcquireTokenByUserFederatedIdentityCredential;
}

/// <inheritdoc/>
protected override void Validate()
{
base.Validate();

if (Parameters.SendX5C == null)
{
Parameters.SendX5C = ServiceBundle.Config?.SendX5C ?? false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,5 +164,28 @@ public async Task<AuthenticationResult> ExecuteAsync(

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

public async Task<AuthenticationResult> ExecuteAsync(
AcquireTokenCommonParameters commonParameters,
AcquireTokenByUserFederatedIdentityCredentialParameters userFicParameters,
CancellationToken cancellationToken)
{
RequestContext requestContext = CreateRequestContextAndLogVersionInfo(commonParameters.CorrelationId, commonParameters.MtlsCertificate, cancellationToken);

AuthenticationRequestParameters requestParams = await _confidentialClientApplication.CreateRequestParametersAsync(
commonParameters,
requestContext,
_confidentialClientApplication.UserTokenCacheInternal,
cancellationToken).ConfigureAwait(false);

requestParams.SendX5C = userFicParameters.SendX5C ?? false;

var handler = new UserFederatedIdentityCredentialRequest(
ServiceBundle,
requestParams,
userFicParameters);

return await handler.RunAsync(cancellationToken).ConfigureAwait(false);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,10 @@ Task<AuthenticationResult> ExecuteAsync(
AcquireTokenCommonParameters commonParameters,
AcquireTokenByUsernamePasswordParameters usernamePasswordParameters,
CancellationToken cancellationToken);

Task<AuthenticationResult> ExecuteAsync(
AcquireTokenCommonParameters commonParameters,
AcquireTokenByUserFederatedIdentityCredentialParameters userFicParameters,
CancellationToken cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client.Core;

namespace Microsoft.Identity.Client.ApiConfig.Parameters
{
internal class AcquireTokenByUserFederatedIdentityCredentialParameters : IAcquireTokenParameters
{
public string Username { get; set; }
public Func<AssertionRequestOptions, Task<string>> AssertionProvider { get; set; }
public bool? SendX5C { get; set; }
public bool ForceRefresh { get; set; }

/// <inheritdoc/>
public void LogParameters(ILoggerAdapter logger)
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Threading.Tasks;
using Microsoft.Identity.Client.AppConfig;

namespace Microsoft.Identity.Client
{
/// <summary>
/// Factory methods to create common <see cref="AssertionRequestOptions"/> delegates for use with
/// <see cref="IByUserFederatedIdentityCredential.AcquireTokenByUserFederatedIdentityCredential"/>.
/// </summary>
#if !SUPPORTS_CONFIDENTIAL_CLIENT
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] // hide confidential client on mobile
#endif
public static class FederatedCredentialProvider
{
private const string DefaultAudience = "api://AzureADTokenExchange/.default";

/// <summary>
/// Creates an assertion provider delegate that acquires a token from a Managed Identity.
/// </summary>
/// <param name="managedIdentityId">
/// The managed identity to use. Use <see cref="ManagedIdentityId.SystemAssigned"/> for system-assigned
/// or <see cref="ManagedIdentityId.WithUserAssignedClientId(string)"/> for user-assigned.
/// </param>
/// <param name="audience">
/// The audience (resource) for which the managed identity token is acquired.
/// Defaults to <c>api://AzureADTokenExchange/.default</c>.
/// </param>
/// <returns>A delegate that acquires a managed identity token and returns its access token string.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="managedIdentityId"/> is null.</exception>
public static Func<AssertionRequestOptions, Task<string>> FromManagedIdentity(
Comment thread
neha-bhargava marked this conversation as resolved.
Outdated
ManagedIdentityId managedIdentityId,
string audience = DefaultAudience)
{
if (managedIdentityId == null)
{
throw new ArgumentNullException(nameof(managedIdentityId));
}

if (audience == null)
{
throw new ArgumentNullException(nameof(audience));
}

// Eagerly build the ManagedIdentityApplication
var miApp = ManagedIdentityApplicationBuilder.Create(managedIdentityId).Build();

return async (options) =>
{
var result = await miApp
.AcquireTokenForManagedIdentity(audience)
.ExecuteAsync(options.CancellationToken)
.ConfigureAwait(false);

return result.AccessToken;
};
}

/// <summary>
/// Creates an assertion provider delegate that acquires a token from a Confidential Client Application.
/// </summary>
/// <param name="cca">The confidential client application to use for token acquisition.</param>
/// <param name="audience">
/// The audience (scope) for which the confidential client acquires a token.
/// Defaults to <c>api://AzureADTokenExchange/.default</c>.
/// </param>
/// <returns>A delegate that acquires a confidential client token and returns its access token string.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="cca"/> is null.</exception>
public static Func<AssertionRequestOptions, Task<string>> FromConfidentialClient(
IConfidentialClientApplication cca,
string audience = DefaultAudience)
{
if (cca == null)
{
throw new ArgumentNullException(nameof(cca));
}

if (audience == null)
{
throw new ArgumentNullException(nameof(audience));
}

return async (options) =>
{
var result = await cca
.AcquireTokenForClient(new[] { audience })
.ExecuteAsync(options.CancellationToken)
.ConfigureAwait(false);

return result.AccessToken;
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public sealed partial class ConfidentialClientApplication
IConfidentialClientApplication,
IByRefreshToken,
ILongRunningWebApi,
IByUsernameAndPassword
IByUsernameAndPassword,
IByUserFederatedIdentityCredential
{
/// <summary>
/// Instructs MSAL to try to auto discover the Azure region.
Expand Down Expand Up @@ -183,6 +184,19 @@ AcquireTokenByUsernameAndPasswordConfidentialParameterBuilder IByUsernameAndPass
password);
}

/// <inheritdoc/>
AcquireTokenByUserFederatedIdentityCredentialParameterBuilder IByUserFederatedIdentityCredential.AcquireTokenByUserFederatedIdentityCredential(
IEnumerable<string> scopes,
string username,
Func<AssertionRequestOptions, Task<string>> assertionProvider)
{
return AcquireTokenByUserFederatedIdentityCredentialParameterBuilder.Create(
ClientExecutorFactory.CreateConfidentialClientExecutor(this),
scopes,
username,
assertionProvider);
}

AcquireTokenByRefreshTokenParameterBuilder IByRefreshToken.AcquireTokenByRefreshToken(
IEnumerable<string> scopes,
string refreshToken)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Microsoft.Identity.Client
{
/// <summary>
/// Provides an interface for acquiring tokens using the User Federated Identity Credential (UserFIC) flow.
/// </summary>
#if !SUPPORTS_CONFIDENTIAL_CLIENT
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] // hide confidential client on mobile
#endif
public interface IByUserFederatedIdentityCredential
{
/// <summary>
/// Acquires a token on behalf of a user using a federated identity credential assertion.
/// This uses the <c>user_fic</c> grant type.
/// </summary>
/// <param name="scopes">Scopes requested to access a protected API.</param>
/// <param name="username">The UPN (User Principal Name) of the user, e.g. <c>john.doe@contoso.com</c>.</param>
/// <param name="assertionProvider">
/// A delegate that returns the federated identity credential assertion (JWT) for the user.
/// Use <see cref="FederatedCredentialProvider"/> to create common implementations.
/// </param>
/// <returns>A builder enabling you to add optional parameters before executing the token request.</returns>
AcquireTokenByUserFederatedIdentityCredentialParameterBuilder AcquireTokenByUserFederatedIdentityCredential(
IEnumerable<string> scopes,
string username,
Func<AssertionRequestOptions, Task<string>> assertionProvider);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Microsoft.Identity.Client
#if !SUPPORTS_CONFIDENTIAL_CLIENT
[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] // hide confidential client on mobile
#endif
public partial interface IConfidentialClientApplication : IClientApplicationBase
public partial interface IConfidentialClientApplication : IClientApplicationBase, IByUserFederatedIdentityCredential
{
/// <summary>
/// Application token cache which holds access tokens for this application. It's maintained
Expand Down
Loading