Skip to content

Commit f00bb55

Browse files
committed
Entra ID integration API proposal
1 parent 3adee5f commit f00bb55

File tree

7 files changed

+1531
-0
lines changed

7 files changed

+1531
-0
lines changed

Aspire.slnx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
<Project Path="src/Aspire.Hosting.Azure.CognitiveServices/Aspire.Hosting.Azure.CognitiveServices.csproj" />
8888
<Project Path="src/Aspire.Hosting.Azure.ContainerRegistry/Aspire.Hosting.Azure.ContainerRegistry.csproj" />
8989
<Project Path="src/Aspire.Hosting.Azure.CosmosDB/Aspire.Hosting.Azure.CosmosDB.csproj" />
90+
<Project Path="src/Aspire.Hosting.Azure.Entra/Aspire.Hosting.Azure.Entra.csproj" />
9091
<Project Path="src/Aspire.Hosting.Azure.EventHubs/Aspire.Hosting.Azure.EventHubs.csproj" />
9192
<Project Path="src/Aspire.Hosting.Azure.Functions/Aspire.Hosting.Azure.Functions.csproj" />
9293
<Project Path="src/Aspire.Hosting.Azure.KeyVault/Aspire.Hosting.Azure.KeyVault.csproj" />
@@ -450,6 +451,7 @@
450451
<Project Path="tests/Aspire.Hosting.Garnet.Tests/Aspire.Hosting.Garnet.Tests.csproj" />
451452
<Project Path="tests/Aspire.Hosting.GitHub.Models.Tests/Aspire.Hosting.GitHub.Models.Tests.csproj" />
452453
<Project Path="tests/Aspire.Hosting.Kafka.Tests/Aspire.Hosting.Kafka.Tests.csproj" />
454+
<Project Path="tests/Aspire.Hosting.Azure.Entra.Tests/Aspire.Hosting.Azure.Entra.Tests.csproj" />
453455
<Project Path="tests/Aspire.Hosting.Keycloak.Tests/Aspire.Hosting.Keycloak.Tests.csproj" />
454456
<Project Path="tests/Aspire.Hosting.Kubernetes.Tests/Aspire.Hosting.Kubernetes.Tests.csproj" />
455457
<Project Path="tests/Aspire.Hosting.Milvus.Tests/Aspire.Hosting.Milvus.Tests.csproj" />
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(DefaultTargetFramework)</TargetFramework>
5+
<IsPackable>true</IsPackable>
6+
<PackageTags>aspire integration hosting azure entra identity authentication security</PackageTags>
7+
<Description>Microsoft Entra ID support for Aspire.</Description>
8+
<SuppressFinalPackageVersion>true</SuppressFinalPackageVersion>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\Aspire.Hosting\Aspire.Hosting.csproj" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<InternalsVisibleTo Include="Aspire.Hosting.Azure.Entra.Tests" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Hosting.ApplicationModel;
5+
6+
namespace Aspire.Hosting.Azure;
7+
8+
/// <summary>
9+
/// A resource that represents a Microsoft Entra ID application registration.
10+
/// </summary>
11+
/// <remarks>
12+
/// <para>
13+
/// Entra ID application registrations define the identity configuration for services
14+
/// in a distributed application. Each application registration includes a tenant ID,
15+
/// client ID, and optionally client credentials and API scopes.
16+
/// </para>
17+
/// <para>
18+
/// This resource injects configuration as environment variables that map to
19+
/// <c>IConfiguration</c> sections compatible with Microsoft.Identity.Web.
20+
/// For example, <c>AzureAd__TenantId</c>, <c>AzureAd__ClientId</c>, etc.
21+
/// </para>
22+
/// <para>
23+
/// The properties align with <c>MicrosoftEntraApplicationOptions</c> from
24+
/// <c>Microsoft.Identity.Abstractions</c>.
25+
/// </para>
26+
/// </remarks>
27+
/// <param name="name">The name of the resource.</param>
28+
/// <param name="configSectionName">
29+
/// The configuration section name used for environment variable prefixes.
30+
/// Defaults to <c>"AzureAd"</c>.
31+
/// </param>
32+
public class EntraIdApplicationResource(string name, string configSectionName = "AzureAd")
33+
: Resource(name), IResourceWithEnvironment
34+
{
35+
private const string DefaultInstance = "https://login.microsoftonline.com/";
36+
37+
/// <summary>
38+
/// Gets the configuration section name used as the prefix for environment variables.
39+
/// </summary>
40+
/// <remarks>
41+
/// Environment variables are injected as <c>{ConfigSectionName}__{Key}</c>, which
42+
/// .NET's configuration system maps to <c>{ConfigSectionName}:{Key}</c> in <c>IConfiguration</c>.
43+
/// </remarks>
44+
public string ConfigSectionName { get; } = configSectionName;
45+
46+
// ── Core identity ──────────────────────────────────────────────────────
47+
48+
/// <summary>
49+
/// Gets or sets the Entra ID instance URL.
50+
/// </summary>
51+
/// <remarks>
52+
/// Defaults to <c>https://login.microsoftonline.com/</c>. Override for sovereign clouds
53+
/// (e.g., <c>https://login.microsoftonline.us/</c> for Azure Government).
54+
/// </remarks>
55+
public string Instance { get; set; } = DefaultInstance;
56+
57+
/// <summary>
58+
/// Gets or sets the parameter resource for the tenant ID.
59+
/// </summary>
60+
public ParameterResource? TenantIdParameter { get; set; }
61+
62+
/// <summary>
63+
/// Gets or sets a fixed tenant ID value.
64+
/// </summary>
65+
/// <remarks>
66+
/// When both <see cref="TenantIdParameter"/> and <see cref="TenantId"/> are set,
67+
/// <see cref="TenantIdParameter"/> takes precedence.
68+
/// </remarks>
69+
public string? TenantId { get; set; }
70+
71+
/// <summary>
72+
/// Gets or sets the parameter resource for the client ID.
73+
/// </summary>
74+
public ParameterResource? ClientIdParameter { get; set; }
75+
76+
/// <summary>
77+
/// Gets or sets a fixed client ID value.
78+
/// </summary>
79+
/// <remarks>
80+
/// When both <see cref="ClientIdParameter"/> and <see cref="ClientId"/> are set,
81+
/// <see cref="ClientIdParameter"/> takes precedence.
82+
/// </remarks>
83+
public string? ClientId { get; set; }
84+
85+
// ── Per-app properties from MicrosoftEntraApplicationOptions ────────
86+
87+
/// <summary>
88+
/// Gets or sets the home tenant of the app registration.
89+
/// </summary>
90+
/// <remarks>
91+
/// Useful for multi-tenant apps that acquire tokens on behalf of themselves.
92+
/// Also provides a direct path to the Azure Portal app registration.
93+
/// </remarks>
94+
public string? AppHomeTenantId { get; set; }
95+
96+
// ── Token acquisition ──────────────────────────────────────────────────
97+
98+
/// <summary>
99+
/// Gets the list of client credentials configured for this application.
100+
/// </summary>
101+
/// <remarks>
102+
/// Supports multiple credential types used by Microsoft.Identity.Web:
103+
/// <list type="bullet">
104+
/// <item><description><c>ClientSecret</c> — application secret</description></item>
105+
/// <item><description><c>SignedAssertionFromManagedIdentity</c> — managed identity credential</description></item>
106+
/// <item><description><c>Certificate</c> — certificate-based credential</description></item>
107+
/// </list>
108+
/// </remarks>
109+
internal List<EntraIdClientCredential> ClientCredentials { get; } = [];
110+
111+
/// <summary>
112+
/// Gets or sets the client capabilities (e.g., <c>"cp1"</c> for Continuous Access Evaluation).
113+
/// </summary>
114+
internal List<string> ClientCapabilities { get; } = [];
115+
116+
/// <summary>
117+
/// Gets or sets the Azure region for optimized token acquisition.
118+
/// </summary>
119+
/// <remarks>
120+
/// Use <c>"TryAutoDetect"</c> to have the app attempt to detect the region automatically.
121+
/// Per deployment — typically the same for all apps in a deployment.
122+
/// </remarks>
123+
public string? AzureRegion { get; set; }
124+
125+
// ── Web API ────────────────────────────────────────────────────────────
126+
127+
/// <summary>
128+
/// Gets the list of audiences accepted by this application registration.
129+
/// </summary>
130+
internal List<string> Audiences { get; } = [];
131+
132+
/// <summary>
133+
/// Gets or sets whether to allow ACL-based authorization for daemon-to-API calls.
134+
/// </summary>
135+
/// <remarks>
136+
/// When <see langword="true"/>, the web API will not require roles or scopes in the token,
137+
/// allowing client credentials flow callers to be authorized by an Access Control List.
138+
/// </remarks>
139+
public bool AllowWebApiToBeAuthorizedByACL { get; set; }
140+
141+
// ── Diagnostics ────────────────────────────────────────────────────────
142+
143+
/// <summary>
144+
/// Gets or sets whether to enable logging of Personally Identifiable Information (PII).
145+
/// </summary>
146+
/// <remarks>
147+
/// The default is <see langword="false"/>. Set to <see langword="true"/> for advanced debugging.
148+
/// PII logs are never written to default outputs.
149+
/// </remarks>
150+
public bool EnablePiiLogging { get; set; }
151+
152+
// ── Query parameters ───────────────────────────────────────────────────
153+
154+
/// <summary>
155+
/// Gets extra query parameters to send to the identity provider.
156+
/// </summary>
157+
/// <remarks>
158+
/// Useful for routing to specific test slices or data centers.
159+
/// </remarks>
160+
internal Dictionary<string, string> ExtraQueryParameters { get; } = [];
161+
}
162+
163+
/// <summary>
164+
/// Represents a client credential entry for an Entra ID application registration.
165+
/// </summary>
166+
/// <remarks>
167+
/// <para>
168+
/// Maps to a single entry in the <c>ClientCredentials</c> array in Microsoft.Identity.Web
169+
/// configuration. Each entry describes one credential source, keyed by <see cref="SourceType"/>.
170+
/// </para>
171+
/// <para>
172+
/// The properties on this class mirror those of <c>CredentialDescription</c> from
173+
/// <c>Microsoft.Identity.Abstractions</c>. Only the properties relevant to the chosen
174+
/// <see cref="SourceType"/> need to be set.
175+
/// </para>
176+
/// </remarks>
177+
public sealed class EntraIdClientCredential
178+
{
179+
/// <summary>
180+
/// Gets or sets the source type of the credential.
181+
/// </summary>
182+
/// <remarks>
183+
/// Valid values include:
184+
/// <list type="bullet">
185+
/// <item><description><c>"ClientSecret"</c> — application secret.</description></item>
186+
/// <item><description><c>"SignedAssertionFromManagedIdentity"</c> — federated identity credential via managed identity.</description></item>
187+
/// <item><description><c>"Certificate"</c> — X.509 certificate provided directly.</description></item>
188+
/// <item><description><c>"KeyVault"</c> — certificate from Azure Key Vault.</description></item>
189+
/// <item><description><c>"Base64Encoded"</c> — certificate from a base64-encoded value.</description></item>
190+
/// <item><description><c>"Path"</c> — certificate from a file path.</description></item>
191+
/// <item><description><c>"StoreWithThumbprint"</c> — certificate from store by thumbprint.</description></item>
192+
/// <item><description><c>"StoreWithDistinguishedName"</c> — certificate from store by distinguished name.</description></item>
193+
/// <item><description><c>"SignedAssertionFilePath"</c> — signed assertion from file (e.g., AKS workload identity).</description></item>
194+
/// <item><description><c>"SignedAssertionFromVault"</c> — signed assertion from Key Vault.</description></item>
195+
/// </list>
196+
/// </remarks>
197+
public required string SourceType { get; set; }
198+
199+
// ── ClientSecret SourceType ────────────────────────────────────────────
200+
201+
/// <summary>
202+
/// Gets or sets the client secret value as a parameter resource.
203+
/// </summary>
204+
/// <remarks>
205+
/// Used when <see cref="SourceType"/> is <c>"ClientSecret"</c>.
206+
/// </remarks>
207+
public ParameterResource? ClientSecret { get; set; }
208+
209+
// ── Managed Identity / FIC ─────────────────────────────────────────────
210+
211+
/// <summary>
212+
/// Gets or sets the managed identity client ID for user-assigned managed identity.
213+
/// </summary>
214+
/// <remarks>
215+
/// Used when <see cref="SourceType"/> is <c>"SignedAssertionFromManagedIdentity"</c>.
216+
/// For system-assigned managed identity, leave this <see langword="null"/>.
217+
/// </remarks>
218+
public string? ManagedIdentityClientId { get; set; }
219+
220+
/// <summary>
221+
/// Gets or sets the token exchange URL for federated identity credential scenarios.
222+
/// </summary>
223+
/// <remarks>
224+
/// If not specified, defaults to <c>api://AzureADTokenExchange</c>.
225+
/// </remarks>
226+
public string? TokenExchangeUrl { get; set; }
227+
228+
/// <summary>
229+
/// Gets or sets the token exchange authority URL.
230+
/// </summary>
231+
/// <remarks>
232+
/// Used when the issuer for token exchange differs from the application's authority.
233+
/// </remarks>
234+
public string? TokenExchangeAuthority { get; set; }
235+
236+
// ── KeyVault SourceType ────────────────────────────────────────────────
237+
238+
/// <summary>
239+
/// Gets or sets the URL of the Azure Key Vault containing the certificate.
240+
/// </summary>
241+
/// <remarks>
242+
/// Used when <see cref="SourceType"/> is <c>"KeyVault"</c> or <c>"SignedAssertionFromVault"</c>.
243+
/// </remarks>
244+
public string? KeyVaultUrl { get; set; }
245+
246+
/// <summary>
247+
/// Gets or sets the name of the certificate in Azure Key Vault.
248+
/// </summary>
249+
/// <remarks>
250+
/// Used in conjunction with <see cref="KeyVaultUrl"/>.
251+
/// </remarks>
252+
public string? KeyVaultCertificateName { get; set; }
253+
254+
// ── Certificate store ──────────────────────────────────────────────────
255+
256+
/// <summary>
257+
/// Gets or sets the certificate store path (e.g., <c>"CurrentUser/My"</c>).
258+
/// </summary>
259+
/// <remarks>
260+
/// Used when <see cref="SourceType"/> is <c>"StoreWithThumbprint"</c> or
261+
/// <c>"StoreWithDistinguishedName"</c>.
262+
/// </remarks>
263+
public string? CertificateStorePath { get; set; }
264+
265+
/// <summary>
266+
/// Gets or sets the certificate thumbprint for store-based lookup.
267+
/// </summary>
268+
/// <remarks>
269+
/// Used when <see cref="SourceType"/> is <c>"StoreWithThumbprint"</c>.
270+
/// </remarks>
271+
public string? CertificateThumbprint { get; set; }
272+
273+
/// <summary>
274+
/// Gets or sets the certificate distinguished name for store-based lookup.
275+
/// </summary>
276+
/// <remarks>
277+
/// Used when <see cref="SourceType"/> is <c>"StoreWithDistinguishedName"</c>.
278+
/// </remarks>
279+
public string? CertificateDistinguishedName { get; set; }
280+
281+
// ── Certificate from file ──────────────────────────────────────────────
282+
283+
/// <summary>
284+
/// Gets or sets the path to a certificate file on disk.
285+
/// </summary>
286+
/// <remarks>
287+
/// Used when <see cref="SourceType"/> is <c>"Path"</c>.
288+
/// Not recommended for production use.
289+
/// </remarks>
290+
public string? CertificateDiskPath { get; set; }
291+
292+
/// <summary>
293+
/// Gets or sets the password for the certificate file.
294+
/// </summary>
295+
/// <remarks>
296+
/// Used in conjunction with <see cref="CertificateDiskPath"/> when the certificate is password-protected.
297+
/// </remarks>
298+
public string? CertificatePassword { get; set; }
299+
300+
// ── Base64-encoded certificate ─────────────────────────────────────────
301+
302+
/// <summary>
303+
/// Gets or sets the base64-encoded certificate value.
304+
/// </summary>
305+
/// <remarks>
306+
/// Used when <see cref="SourceType"/> is <c>"Base64Encoded"</c>.
307+
/// Not recommended for production use.
308+
/// </remarks>
309+
public string? Base64EncodedValue { get; set; }
310+
311+
// ── Signed assertion file (AKS workload identity) ──────────────────────
312+
313+
/// <summary>
314+
/// Gets or sets the path to a signed assertion file on disk.
315+
/// </summary>
316+
/// <remarks>
317+
/// Used when <see cref="SourceType"/> is <c>"SignedAssertionFilePath"</c>.
318+
/// Typically used with Azure Kubernetes Service workload identity federation.
319+
/// If not provided, the <c>AZURE_FEDERATED_TOKEN_FILE</c> environment variable is used.
320+
/// </remarks>
321+
public string? SignedAssertionFileDiskPath { get; set; }
322+
}

0 commit comments

Comments
 (0)