Skip to content

Entra ID integration API proposal for discussion#14668

Open
jmprieur wants to merge 11 commits intomicrosoft:mainfrom
jmprieur:jmprieur/SpecEntraIdIntegration
Open

Entra ID integration API proposal for discussion#14668
jmprieur wants to merge 11 commits intomicrosoft:mainfrom
jmprieur:jmprieur/SpecEntraIdIntegration

Conversation

@jmprieur
Copy link
Copy Markdown
Member

@jmprieur jmprieur commented Feb 25, 2026

Description

AppHost model to integrate Entra ID applications. This is the source of truth that all surfaces read (provisioning Skill, MCP, Aspire CLI). It creates the model that makes intelligent provisioning possible.

classDiagram
    class Resource {
        <<Aspire.Hosting>>
        +string Name
    }

    class IResourceWithEnvironment {
        <<interface>>
    }

    class EntraIdApplicationResource {
        +string ConfigSectionName
        +string Instance
        +ParameterResource? TenantIdParameter
        +string? TenantId
        +ParameterResource? ClientIdParameter
        +string? ClientId
        +string? AppHomeTenantId
        +string? AzureRegion
        +bool AllowWebApiToBeAuthorizedByACL
        ~List~EntraIdClientCredential~ ClientCredentials
        ~List~string~ ClientCapabilities
        ~List~string~ Audiences
        ~Dictionary~string,string~ ExtraQueryParameters
    }

    class EntraIdClientCredential {
        <<abstract>>
        +string SourceType*
        ~EmitEnvironmentVariables()*
    }

    class EntraIdClientSecretCredential {
        +SourceType = "ClientSecret"
        +ParameterResource ClientSecret
    }

    class EntraIdFederatedIdentityCredential {
        +SourceType = "SignedAssertionFromManagedIdentity"
        +string? ManagedIdentityClientId
        +string? TokenExchangeUrl
        +string? TokenExchangeAuthority
    }

    class EntraIdKeyVaultCertificateCredential {
        +SourceType = "KeyVault"
        +string KeyVaultUrl
        +string CertificateNameInKeyVault
    }

    class EntraIdStoreCertificateCredential {
        +SourceType = "StoreWith..."
        +string StorePath
        +string? Thumbprint
        +string? DistinguishedName
    }

    class EntraIdFileCertificateCredential {
        +SourceType = "Path"
        +string FilePath
        +string? Password
    }

    class EntraIdSignedAssertionFileCredential {
        +SourceType = "SignedAssertionFilePath"
        +string? FilePath
    }

    class EntraIdResourceExtensions {
        <<static>>
        +AddEntraIdApplication()$
        +AsExisting(tenantId, clientId)$
        +WithClientSecret()$
        +WithFicMsi()$
        +WithCertificateFromKeyVault()$
        +WithCertificateThumbprint()$
        +WithCertificateDistinguishedName()$
        +WithCredential()$
        +WithInstance()$
        +WithAppHomeTenantId()$
        +WithClientCapability()$
        +WithAzureRegion()$
        +WithAllowWebApiToBeAuthorizedByACL()$
        +WithExtraQueryParameter()$
        +WithAudience()$
        +WithEntraIdAuthentication()$
    }

    Resource <|-- EntraIdApplicationResource
    IResourceWithEnvironment <|.. EntraIdApplicationResource
    EntraIdApplicationResource *-- "0..*" EntraIdClientCredential : ClientCredentials

    EntraIdClientCredential <|-- EntraIdClientSecretCredential
    EntraIdClientCredential <|-- EntraIdFederatedIdentityCredential
    EntraIdClientCredential <|-- EntraIdKeyVaultCertificateCredential
    EntraIdClientCredential <|-- EntraIdStoreCertificateCredential
    EntraIdClientCredential <|-- EntraIdFileCertificateCredential
    EntraIdClientCredential <|-- EntraIdSignedAssertionFileCredential

    EntraIdResourceExtensions ..> EntraIdApplicationResource : creates & configures

    note for EntraIdResourceExtensions "WithReference() injects\nenv vars like AzureAd__TenantId\ninto consuming services"
    note for EntraIdClientCredential "Each subtype emits its own\nenv vars via EmitEnvironmentVariables()"
Loading

Fixes # (issue)

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • [ x ] Yes
    • No
  • Did you add public API?
    • [ x ] Yes
      • If yes, did you have an API Review for it?
        • Yes
        • [ x] No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 25, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14668

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14668"

@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Feb 25, 2026
Comment thread src/Aspire.Hosting.Azure.Entra/EntraIdApplicationResource.cs Outdated
Comment thread src/Aspire.Hosting.Azure.EntraId/EntraIdApplicationResource.cs
Comment thread src/Aspire.Hosting.Azure.EntraId/EntraIdResourceExtensions.cs
Comment thread aspire.code-workspace Outdated
Copy link
Copy Markdown
Member

@IEvangelist IEvangelist left a comment

Choose a reason for hiding this comment

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

This looks really good so far. A few minor nits, while I looked at the code quick.

Comment thread src/Aspire.Hosting.Azure.EntraId/README.md
Comment thread src/Aspire.Hosting.Azure.EntraId/README.md Outdated
Co-authored-by: David Pine <david.pine@microsoft.com>
@jmprieur jmprieur marked this pull request as ready for review April 21, 2026 00:35
Copilot AI review requested due to automatic review settings April 21, 2026 00:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR proposes a new Aspire Hosting integration surface for modeling Microsoft Entra ID application registrations in the AppHost model, enabling other “surfaces” (provisioning skill/MCP/CLI) to consume a single source of truth.

Changes:

  • Adds EntraIdApplicationResource + credential subtypes to represent Entra ID app registration configuration in the application model.
  • Adds EntraIdResourceExtensions fluent APIs (AddEntraIdApplication, AsExisting, credential helpers, and WithReference env-var injection).
  • Introduces a new hosting integration README and a new test project with builder/unit tests; wires projects into Aspire.slnx.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/Aspire.Hosting.Azure.EntraId/EntraIdApplicationResource.cs New resource + credential model types for Entra ID app registrations.
src/Aspire.Hosting.Azure.EntraId/EntraIdResourceExtensions.cs New fluent builder APIs and WithReference env-var injection logic.
src/Aspire.Hosting.Azure.EntraId/README.md New hosting integration README describing intended usage.
src/Aspire.Hosting.Azure.EntraId/Aspire.Hosting.Azure.EntraId.csproj New packable hosting integration project + InternalsVisibleTo for tests.
tests/Aspire.Hosting.Azure.EntraId.Tests/EntraIdResourceBuilderTests.cs New unit tests for resource/builder behavior.
tests/Aspire.Hosting.Azure.EntraId.Tests/Aspire.Hosting.Azure.EntraId.Tests.csproj New test project referencing the integration package and shared hosting tests infra.
Aspire.slnx Adds the new project and test project to the solution.

Comment on lines +354 to +355

if (DistinguishedName is not null)
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

EmitEnvironmentVariables will emit a thumbprint when set, and (in the following block) also emits a distinguished name when set. If both properties are set, both values will be emitted even though only one identifier should be used for the selected SourceType. Consider validating/normalizing so only the intended identifier is emitted.

Suggested change
if (DistinguishedName is not null)
else if (DistinguishedName is not null)

Copilot uses AI. Check for mistakes.
Comment on lines +196 to +200
public static IResourceBuilder<EntraIdApplicationResource> WithClientSecret(
this IResourceBuilder<EntraIdApplicationResource> builder,
IResourceBuilder<ParameterResource> clientSecret)
{
ArgumentNullException.ThrowIfNull(builder);
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

WithClientSecret accepts any ParameterResource, but this value represents a credential and will be injected into consuming services. Other integrations validate that sensitive inputs are marked secret: true (e.g., OpenAI WithApiKey). Consider checking clientSecret.Resource.Secret and throwing if it isn’t secret to prevent accidental exposure of non-secret parameters as credentials.

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +371
public void WithReference_InjectsEnvironmentVariables()
{
using var appBuilder = TestDistributedApplicationBuilder.Create();

var secret = appBuilder.AddParameter("EntraSecret", secret: true);
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

WithReference_InjectsEnvironmentVariables only asserts that an EnvironmentCallbackAnnotation exists, but it doesn’t execute the callback or verify any emitted keys/values. Consider invoking the callback(s) with an EnvironmentCallbackContext (as done in other hosting tests) and asserting on a representative set of variables so regressions in the env-var mapping are caught.

Copilot uses AI. Check for mistakes.
Comment on lines +321 to +325
throw new InvalidOperationException(
$"Either {nameof(Thumbprint)} or {nameof(DistinguishedName)} must be set on {nameof(EntraIdStoreCertificateCredential)}.");
}

return Thumbprint is not null ? "StoreWithThumbprint" : "StoreWithDistinguishedName";
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

EntraIdStoreCertificateCredential is documented as “set either Thumbprint or DistinguishedName (not both)”, but SourceType currently picks StoreWithThumbprint whenever Thumbprint is set even if DistinguishedName is also set. Consider throwing when both are set (or otherwise enforcing mutual exclusivity) to avoid silently producing inconsistent configuration.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +36
public class EntraIdApplicationResource(string name, string configSectionName = "AzureAd")
: Resource(name), IResourceWithEnvironment
{
private const string DefaultInstance = "https://login.microsoftonline.com/";

Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

EntraIdApplicationResource is public and can be constructed directly with a configSectionName, but there’s no validation to prevent null/empty values. If ConfigSectionName is null/empty, WithReference will emit malformed environment variable keys. Consider validating configSectionName in the constructor so the resource is always in a valid state.

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +305
public static IResourceBuilder<EntraIdApplicationResource> WithCertificateThumbprint(
this IResourceBuilder<EntraIdApplicationResource> builder,
string storePath,
string thumbprint)
{
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

WithCertificateThumbprint introduces behavior/config shape but there’s no unit test validating the resulting EntraIdStoreCertificateCredential state (including SourceType, StorePath, and Thumbprint). Adding a test similar to the distinguished-name test would help prevent regressions (especially given the SourceType selection logic).

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +24
In your service projects, install Microsoft.Identity.Web:

```dotnetcli
dotnet add package Microsoft.Identity.Web
```
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

This hosting integration README includes client-project guidance (installing Microsoft.Identity.Web). The repo’s hosting integration README guidance typically keeps these READMEs focused on AppHost usage (Add* / WithReference) and links out for client integration details. Consider moving this client-package installation guidance to a client integration README or “Additional documentation”.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +48
In the API project's _Program.cs_, use Microsoft.Identity.Web directly — the configuration is automatically available via the `AzureAd` section:

```csharp
builder.Services.AddAuthentication()
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
```
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The README includes service Program.cs authentication wiring examples (AddMicrosoftIdentityWebApi / AddMicrosoftIdentityWebApp). For hosting integration READMEs, we generally try to avoid DI/container setup details and instead focus on the AppHost surface and what WithReference injects. Consider moving these snippets to a dedicated client integration doc and keep this README centered on the AppHost model API.

Copilot uses AI. Check for mistakes.
internal List<EntraIdClientCredential> ClientCredentials { get; } = [];

/// <summary>
/// Gets or sets the client capabilities (e.g., <c>"cp1"</c> for Continuous Access Evaluation).
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The XML doc for ClientCapabilities says “Gets or sets…”, but the property is getter-only. Consider updating the summary to “Gets the client capabilities…” to keep the docs accurate.

Suggested change
/// Gets or sets the client capabilities (e.g., <c>"cp1"</c> for Continuous Access Evaluation).
/// Gets the client capabilities (e.g., <c>"cp1"</c> for Continuous Access Evaluation).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution Indicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants