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 support for Managed Identity #108

Merged
merged 8 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Nullable>enable</Nullable>
<LangVersion>9.0</LangVersion>
<PackageVersion>5.0.0</PackageVersion>
<PackageVersion>5.1.0</PackageVersion>
<EnablePackageValidation>true</EnablePackageValidation>
<PackageValidationBaselineVersion>5.0.0</PackageValidationBaselineVersion>
</PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public string? Container
CredentialSource.StoreWithThumbprint or CredentialSource.StoreWithDistinguishedName => CertificateStorePath,
CredentialSource.SignedAssertionFilePath => SignedAssertionFileDiskPath,
CredentialSource.SignedAssertionFromVault => KeyVaultUrl,
_ => null,
_ => null
};
}
set
Expand Down Expand Up @@ -257,8 +257,8 @@ public string? Container
public string? ClientSecret { get; set; }

/// <summary>
/// When <see cref="SourceType"/> is <see cref="CredentialSource.SignedAssertionFromManagedIdentity"/>, specifies the client ID of the Azure user-assigned managed identity
/// used to provide an signed assertion that will be used as a client credential for the application. This requires that the application is deployed on Azure, that the managed identity is configured,
/// When <see cref="SourceType"/> is <see cref="CredentialSource.SignedAssertionFromManagedIdentity"/>, it specifies the client ID of the Azure user-assigned managed identity
/// used to provide a signed assertion to act as a client credential for the application. This requires that the application is deployed on Azure, that the managed identity is configured,
/// and that workload identity federation with the managed identity is declared in the application registration. For details, see https://learn.microsoft.com/azure/active-directory/workload-identities/workload-identity-federation.
/// </summary>
/// <example>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public class IdentityApplicationOptions
/// <summary>
/// In a web API, accepted audiences for the tokens received by the web API.
/// <para>See also <see cref="Audience"/>.</para>
/// The audience is the intended recipient of the token. You can usually assume that the ApplicationId of your web API
/// The audience is the intended recipient of the token. You can usually assume that the ApplicationID of your web API
/// is a valid audience. It can, in general be any of the App ID URIs (or resource identitfier) you defined for your application
/// during its registration in the Azure portal.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.ComponentModel;

namespace Microsoft.Identity.Abstractions
{
/// <summary>
/// Data object to hold the definition of a managed identity for an application to use for authentication.
/// See <see href="https://aka.ms/Entra/ManagedIdentityOverview"/> for more details.
/// </summary>
public class ManagedIdentityOptions
{
/// <summary>
/// Gets or sets whether the <see cref="Abstractions.ManagedIdentityType"/> is system-assigned or user assigned.
/// Defaults to <see cref="ManagedIdentityType.SystemAssigned"/> if not set.
/// See <see href="https://aka.ms/Entra/ManagedIdentityOverview"/> for details on these two types of managed identity.
/// </summary>
[DefaultValue(ManagedIdentityType.SystemAssigned)]
public ManagedIdentityType ManagedIdentityType { get; set; }

/// <summary>
/// Gets or sets the value of the ClientID when <see cref="ManagedIdentityType"/> is set to
/// <see cref="ManagedIdentityType.UserAssigned"/>. If not set, the default value is null.
/// </summary>
public string? ClientId { get; set; }

/// <summary>
/// Makes a new object to avoid sharing the same reference.
/// </summary>
/// <returns>A new instance of <see cref="ManagedIdentityOptions"/> with the same field values.</returns>
public ManagedIdentityOptions Clone()
{
return new ManagedIdentityOptions
{
ManagedIdentityType = ManagedIdentityType,
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
ClientId = ClientId
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

namespace Microsoft.Identity.Abstractions
{
/// <summary>
/// Used by <see cref="ManagedIdentityOptions"/> to specify the type of managed identity to use.
/// See <see href="https://aka.ms/Entra/ManagedIdentityOverview"/> for more details.
/// </summary>
public enum ManagedIdentityType
{
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
/// <summary>
/// The default value, indicating the managed identity to use is the one configured for the Azure resource on which the
JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
/// application is running.
/// </summary>
SystemAssigned = 0,

/// <summary>
/// Indicates the managed identity to use is a user-assigned identity which is defined in a standalone Azure resource.
/// </summary>
UserAssigned = 1,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,21 @@ public AcquireTokenOptions(AcquireTokenOptions other)
Claims = other.Claims;
PopPublicKey = other.PopPublicKey;
PopClaim = other.PopClaim;
ManagedIdentity = other.ManagedIdentity?.Clone();
LongRunningWebApiSessionKey = other.LongRunningWebApiSessionKey;
Tenant = other.Tenant;
UserFlow = other.UserFlow;
}

/// <summary>
/// Gets the name of the options describing the confidential client application (ClientId,
/// Gets the name of the options describing the confidential client application (ClientID,
/// Region, Authority, client credentials). In ASP.NET Core, the authenticatiopn options name
/// is the same as the authentication scheme.
/// </summary>
public string? AuthenticationOptionsName { get; set; }

/// <summary>
/// Sets the correlation id to be used in the request to the STS "/token" endpoint.
/// Sets the correlation ID to be used in the request to the STS "/token" endpoint.
/// </summary>
public Guid? CorrelationId { get; set; }

Expand All @@ -64,7 +65,8 @@ public AcquireTokenOptions(AcquireTokenOptions other)
/// A string with one or multiple claims to request. It's a json blob (encoded or not)
/// Normally used with Conditional Access. It receives the Claims member of the UiRequiredException.
/// It can also be used to request specific optional claims, and for
/// <see href="https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/concept-conditional-access-cloud-apps">CA Auth context</see>
/// <see href="https://learn.microsoft.com/en-us/azure/active-directory/conditional-access/concept-conditional-access-cloud-apps">
/// CA Auth context</see>
/// </summary>
public string? Claims { get; set; }

Expand All @@ -90,6 +92,36 @@ public AcquireTokenOptions(AcquireTokenOptions other)
/// </summary>
public string? PopClaim { get; set; }

/// <summary>
/// When <see cref="ManagedIdentity"/> is set, the application uses a managed identity instead of client credentials to
/// acquire an app token.
/// The type of managed identity is defined by the <see cref="ManagedIdentityOptions.ManagedIdentityType"/> field. When
/// using a <see cref="ManagedIdentityType.SystemAssigned"/> identity, this is the only field that needs to be set and is
/// set by default. However, for readability it can be useful to set explicitly.
/// To use a user-assigned identity, select the <see cref="ManagedIdentityType"/> that corresponds to the
/// <see cref="ManagedIdentityOptions.ClientId"/> you plan to use for authentication.
/// Using either form of managed identity requires the application to be deployed on Azure and
/// the managed identity to be configured. For more details, check the
/// <see href="https://aka.ms/Entra/ManagedIdentityOverview"> managed identities for Azure documentation</see>.
/// </summary>
/// <example>
/// <format type="text/markdown">
/// <![CDATA[
/// The Json fragment below describes how to use a system-assigned Managed Identity for authentication in a confidential client application :
/// :::code language="json" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentitysystem_json":::
///
/// The code below describes the same, programmatically in C#.
/// :::code language="csharp" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentitysystem_csharp":::
///
/// The Json fragment below describes how to use a user-assigned Managed Identity for authentication in a confidential client application :
/// :::code language="json" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentityuser_json":::
///
/// The code below describes the same, programmatically in C#.
/// :::code language="csharp" source="~/../abstractions-samples/test/Microsoft.Identity.Abstractions.Tests/AquireTokenOptions.cs" id="managedidentityuser_csharp":::
/// ]]></format>
/// </example>
public ManagedIdentityOptions? ManagedIdentity { get; set; }

/// <summary>
/// Key used for long running web APIs that need to call downstream web
/// APIs on behalf of the user. Can be null, if you are not developing a long
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Xunit;

namespace Microsoft.Identity.Abstractions.Tests
{
public class AquireTokenOptionsTests
{
[Fact]
public void ManagedIdentitySystemAssigned()
{
// App token from a system-assigned managed identity.
// -------------------------------------------------
// https://aka.ms/Entra/ManagedIdentityOverview
/*
// <managedidentity_json>
{
"AquireTokenOptions": {
"ManagedIdentity": {
"ManagedIdentityType": "SystemAssigned"
}
}
}
// </managedidentitysystem_json>
*/

// <managedidentity_csharp>
AcquireTokenOptions acquireTokenOptions = new AcquireTokenOptions
{
ManagedIdentity = new ManagedIdentityOptions()
{
// default: ManagedIdentityType = ManagedIdentityType.SystemAssigned
}
};
// </managedidentitysystem_csharp>

Assert.Equal(ManagedIdentityType.SystemAssigned, acquireTokenOptions.ManagedIdentity.ManagedIdentityType);
Assert.Null(acquireTokenOptions.ManagedIdentity.ClientId);
}

[Fact]
public void ManagedIdentityUserAssigned()
{
// App token from a user-assigned managed identity.
// -------------------------------------------------
// https://aka.ms/Entra/ManagedIdentityOverview
/*
// <managedidentityuser_json>
{
"AquireTokenOptions": {
jmprieur marked this conversation as resolved.
Show resolved Hide resolved
"ManagedIdentity": {
"ManagedIdentityType": "UserAssigned"
"ClientId": "[ClientIdForTheManagedIdentityResource]"
}
}
}
// </managedidentityuser_json>
*/

// <managedidentityuser_csharp>
ManagedIdentityOptions managedIdentityDescription = new ManagedIdentityOptions
{
ManagedIdentityType = ManagedIdentityType.UserAssigned,
ClientId = "[ClientIdForTheManagedIdentityResource]"
};

AcquireTokenOptions acquireTokenOptions = new AcquireTokenOptions
{
ManagedIdentity = managedIdentityDescription
};
// </managedidentityuser_csharp>

JoshLozensky marked this conversation as resolved.
Show resolved Hide resolved
Assert.Equal(ManagedIdentityType.UserAssigned, acquireTokenOptions.ManagedIdentity.ManagedIdentityType);
Assert.Equal(managedIdentityDescription.ClientId, acquireTokenOptions.ManagedIdentity.ClientId);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public void CloneClonesAllProperties()
ExtraQueryParameters = new Dictionary<string, string> { { "slice", "test" } },
ForceRefresh = true,
LongRunningWebApiSessionKey = AcquireTokenOptions.LongRunningWebApiSessionKeyAuto,
ManagedIdentity = new ManagedIdentityOptions(),
PopPublicKey = "PopKey",
PopClaim = "jwkClaim",
Tenant = "domain.com",
Expand Down Expand Up @@ -75,14 +76,16 @@ public void CloneClonesAllProperties()
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ExtraQueryParameters, downstreamApiClone.AcquireTokenOptions.ExtraQueryParameters);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ForceRefresh, downstreamApiClone.AcquireTokenOptions.ForceRefresh);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.LongRunningWebApiSessionKey, downstreamApiClone.AcquireTokenOptions.LongRunningWebApiSessionKey);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ManagedIdentity.ManagedIdentityType, downstreamApiClone.AcquireTokenOptions.ManagedIdentity?.ManagedIdentityType);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.ManagedIdentity.ClientId, downstreamApiClone.AcquireTokenOptions.ManagedIdentity?.ClientId);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.PopPublicKey, downstreamApiClone.AcquireTokenOptions.PopPublicKey);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.PopClaim, downstreamApiClone.AcquireTokenOptions.PopClaim);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.Tenant, downstreamApiClone.AcquireTokenOptions.Tenant);
Assert.Equal(downstreamApiOptions.AcquireTokenOptions.UserFlow, downstreamApiClone.AcquireTokenOptions.UserFlow);

// If this fails, think of also adding a line to test the new property
Assert.Equal(10, typeof(DownstreamApiOptions).GetProperties().Length);
Assert.Equal(12, typeof(AcquireTokenOptions).GetProperties().Length);
Assert.Equal(13, typeof(AcquireTokenOptions).GetProperties().Length);

DownstreamApiOptionsReadOnlyHttpMethod options = new DownstreamApiOptionsReadOnlyHttpMethod(downstreamApiOptions, HttpMethod.Delete.ToString());
Assert.Equal(HttpMethod.Delete.ToString(), options.HttpMethod);
Expand Down Expand Up @@ -152,4 +155,4 @@ public CustomAcquireTokenOptions() : base() { }

public CustomAcquireTokenOptions(CustomAcquireTokenOptions other) : base(other) { }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Xunit;

namespace Microsoft.Identity.Abstractions.Tests
{
public class ManagedIdentityDescriptionTests
{
/// <summary>
/// If no field is set for <see cref="ManagedIdentityOptions"/> the <see cref="ManagedIdentityOptions.ManagedIdentityType"/>
/// field needs to default to <see cref="ManagedIdentitySource.SystemAssigned"/> as other Microsoft.Identity libraries
/// will depend on this.
/// </summary>
[Fact]
public void ManagedIdentity_NoDescriptionFieldsSet()
{
// Arrange
ManagedIdentityOptions description = new();

// Assert
Assert.Equal(ManagedIdentityType.SystemAssigned, description.ManagedIdentityType);
Assert.Null(description.ClientId);
}
}
}
Loading