Skip to content

Provision Microsoft Entra registrations using Azure.Provisioning - #20937

Merged
Eric Erhardt (eerhardt) merged 19 commits into
mainfrom
eerhardt-entra-bicep-registrations
Oct 16, 2026
Merged

Eric Erhardt (eerhardt) merged 19 commits into
mainfrom
eerhardt-entra-bicep-registrations

Conversation

@eerhardt

@eerhardt Eric Erhardt (eerhardt) commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Description

Allow C# AppHosts to create Microsoft Entra application registrations and service principals through Aspire's Azure provisioning infrastructure. AddEntraIdApplication(...).AsExistingApplication(...) references an existing registration while retaining the normal Azure provisioning setup. The new top-level AddExistingEntraApplication(...) provides a configuration-only reference without enabling Azure provisioning or requiring Graph access. Both existing-reference APIs accept literal IDs or parameter resources. Builds on merged #14668.

This is the registration-creation foundation, not the complete web-to-API authentication workflow. Automatic redirect URI discovery, credential creation/rotation, API scopes/permissions/consent, and federated identity trust provisioning remain follow-ups. The integration is currently C#-only. No separate issue is closed by this PR.

Usage and behavior

var webIdentity = builder.AddEntraIdApplication("entra-web")
    .WithRedirectUris(
        "https://localhost:7123/signin-oidc",
        "https://localhost:7123/signout-callback-oidc");

builder.AddProject<Projects.Web>("web")
    .WithReference(webIdentity);

Created registrations supply client and home-tenant IDs through existing Bicep output references. No implicit readiness/health wait or Azure controller failure/retry refactor is introduced. Caller-added waits remain intact.

Registration settings are emitted directly from resource properties. WithIdTokenIssuance() enables implicit/hybrid ID-token issuance when needed; it is disabled by default and unnecessary for authorization-code-only flows. Callback URIs must be supplied explicitly.

The generated Entra module has only the tenantResourceSuffix deployment parameter. It has no location or owner-principal parameter and no explicit owner assignments. Microsoft Entra's default ownership behavior applies. This avoids requiring an application/RBAC principalId when native aspire deploy provisions the registration. Explicit owner configuration is tracked separately in #21030.

Provisioning and publishing omit implicit location arguments for generated modules that do not declare location; other modules retain existing behavior. Entra ARM deployments remain resource-group scoped. Explicit subscription/tenant deployment support is not included.

Naming, provisioning, and cleanup

  • The Azure provisioning dialog and Change Azure context offer an optional tenant-resource suffix for participating provisioned resources. It is persisted, restored, and clearable; Azure:TenantResourceSuffix supplies its initial value.
  • Empty suffix uses the resource name; dev-a produces entra-web-dev-a. Logical Aspire names and consumer configuration prefixes remain unchanged. Directory identity is determined by tenant/name/suffix, not the resource group.
  • Published templates expose the suffix with an empty default rather than embedding a developer's local setting. Explicit module values take precedence. Direct deployment binds it from Azure configuration. Changing it invalidates cached outputs, targets another registration, and leaves prior objects intact.
  • Generate Graph application/service-principal resources and outputs through Azure.Provisioning 1.6.0. Experimental BicepExtensions declarations use SDK escaping and pin br:mcr.microsoft.com/bicep/extensions/microsoftgraph/v1.0:1.0.0. Bicep 0.36.1+ is required; normal compilation restores the extension. Native SDK support is requested in Azure/azure-sdk-for-net#63848.
  • Experimental TenantScopedResourceAnnotation identifies tenant-wide non-ARM objects and carries cleanup identification outputs. It is not ARM deployment-scope metadata.
  • Retain Entra reprovision/cancel/local-state commands while excluding ARM inspection/location/deletion and drift probes.
  • Allow environment resource-group deletion in mixed models. Preserve directory outputs/state/links and report remaining names/types/object IDs/tenant IDs; reset ordinary ARM state. Reports use stored outputs, not live Graph verification; missing IDs remain unknown.
  • Existing references do not mutate registrations. Creation requires Azure deployment and appropriate Graph permissions. No API permissions, credentials, or tenant consent are created automatically. WithFicMsi requires a user-assigned managed identity client ID and emits configuration only, not identity assignment or federation trust.

The playground supports existing/create-new modes selected in AppHost code, dashboard-driven provisioning setup, and a manifest launch profile. Its existing-only mode uses AddExistingEntraApplication without enabling Azure provisioning. Generated local artifacts are not committed.

Generic manifest scalar/deferred fixes, publisher typed/deferred fixes, automatic parameter-type inference, and EvaluatedParameters were reverted as outside this feature's scope. The attempted readiness/controller refactor was also reverted. Shared parameter changes are limited to the agreed suffix handling and omission of undeclared location arguments. The configuration-only factory does not change Azure core registration or pipeline behavior.

Latest existing-reference validation — c41e812914

Validation used a task-owned temporary output directory; $output is its full path.

Command Result
dotnet test --project .\tests\Aspire.Hosting.Azure.EntraId.Tests\Aspire.Hosting.Azure.EntraId.Tests.csproj --no-restore --no-launch-profile -p:BuildInParallel=false "-p:OutDir=$output\" --verbosity quiet -- --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true" Passed: 244 tests, zero failures/skips
dotnet build .\playground\EntraId\EntraId.AppHost\EntraId.AppHost.csproj --no-restore -p:SkipNativeBuild=true -p:BuildInParallel=false "-p:OutDir=$output\playground\" --verbosity quiet Passed; existing transitive NuGet audit advisories emitted

The regression tests execute an existing-only deploy pipeline for both literal and parameter factory overloads, verify that Azure authentication/context steps are not selected, and fail if Azure credentials are requested. They also cover preservation of the fluent API's Azure setup, application pipeline customizations, and mixed Azure/created-Entra models in both addition orders. Azure core files are unchanged by this factory addition.

The earlier native deployment E2E run at 57f9767b8e passed with 1 executed, 1 passed, and 0 skipped. It covered owner-omitted application/service-principal creation, the output-backed consumer environment and live HTTPS response, stable IDs after a redirect-URI update, and separate directory/resource-group cleanup. The CI identity's retrieved Graph application grants contained Application.ReadWrite.OwnedBy, not Application.ReadWrite.All. This live run predates the new top-level factory; no new live deployment at c41e812914 is claimed.

Earlier source validation — 410e03a55e

Validation used a task-owned temporary output directory to avoid interfering with developer processes. $output is the directory created with Directory.CreateTempSubdirectory(...); it was removed afterward.

Command Result
dotnet test --project .\tests\Aspire.Hosting.Azure.EntraId.Tests\Aspire.Hosting.Azure.EntraId.Tests.csproj --no-restore --no-launch-profile -p:BuildInParallel=false "-p:OutDir=$($output.FullName)\" -- --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true" Passed: 228 tests, zero failures/skips
dotnet build .\src\Aspire.Hosting.Azure.EntraId\Aspire.Hosting.Azure.EntraId.csproj -c Release --no-restore -p:SkipNativeBuild=true -p:BuildInParallel=false -m:1 --verbosity minimal Passed for the owner-removal source change
az bicep build --file <temporary-main.bicep> --outfile <temporary-main.json> --only-show-errors Passed for owner-free parent/Graph/consumer artifacts; compiled Graph parameter shape contains only tenantResourceSuffix
git diff --check and git diff --cached --check Passed before publication

Separate publisher/manifest snapshots cover owner/principal removal. Basic Bicep generation is mode-independent and uses a single fact rather than duplicate run/publish snapshots.

At preceding head b8914b3478, the following command also passed 233 affected Azure tests: dotnet test --project .\tests\Aspire.Hosting.Azure.Tests\Aspire.Hosting.Azure.Tests.csproj --no-restore --no-launch-profile -p:BuildInParallel=false "-p:OutDir=$($output.FullName)\" -- --filter-class "*.AzureBicepProvisionerTests" --filter-class "*.AzureProvisioningResourceTests" --filter-class "*.AzureEnvironmentResourceTests" --filter-class "*.AzureInfrastructureExtensionsTests" --filter-class "*.AzureEnvironmentResourceExtensionsTests" --filter-class "*.ProvisioningContextProviderTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true". Shared Azure provisioning code is unchanged by the owner-removal commit.

Existing transitive NuGet audit advisories are emitted. No owner-removal-head native AOT/full-solution build or native aspire deploy live success is claimed. The locally patched Release DLL successfully published the retained test AppHost without an Entra principal parameter. Current GitHub checks must be read from the PR; local validation does not imply CI is green. Formal API/security review remains outstanding.

Earlier live and artifact evidence

Matching b8914b3478 dogfood artifacts were verified through real Graph creation/settings/ownership, a healthy .NET consumer and dashboard, cached restart without redeployment, stable-ID callback updates, unmodified published parent/nested deployment with a real output-backed consumer, and suffix isolation followed by restoration of the original identity. User-requested test apps and cloud resources were retained privately for inspection.

Those live scenarios explicitly assigned owners and do not establish owner-omitted creation/update behavior or successful native aspire deploy. The later user-reported native deploy principal-resolution failure motivated removing default owner assignment and its parameter.

Earlier artifact testing at 8a8164c5 also covered eight fresh projects and ten approved scenarios; those task-owned resources were cleaned up. Neither historical run substitutes for testing subsequent revisions. Live destructive cleanup reporting and completed interactive browser authentication remain unverified at the current revision. Private tenant/resource/log evidence is not uploaded.

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?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • 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

Generate Microsoft Graph Bicep extension resources and preserve typed deployment parameters. Keep existing registrations configuration-only and validate Identity.Web credential configuration.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60
Full managed solution build completed with zero warnings/errors. All 207 Entra tests and 98 affected Azure tests passed. Real AppHost provisioning, stable-ID updates/cache reuse, existing-only and safe failure paths, published nested deployment/output propagation, and task-owned cleanup were verified.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60
@github-actions

Copy link
Copy Markdown
Contributor

🚀 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 -- 20937

Or

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

@aspire-repo-bot
aspire-repo-bot Bot requested a balanced review from Copilot October 12, 2026 23:17
@github-actions github-actions Bot added the area-integrations Issues pertaining to Aspire Integrations packages label Oct 12, 2026
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Deferred and manifest Boolean parameters can be emitted with incorrect values or types.

2 open findings
What changed in this PR

Adds Azure-backed provisioning for Microsoft Entra application registrations while preserving configuration-only references to existing registrations.

Changes:

  • Generates Graph application and service-principal Bicep resources.
  • Adds inline Bicep extensions and structured module parameters.
  • Expands validation, documentation, and provisioning tests.
File Description
tests/​Aspire.Hosting.Azure.Tests/​Snapshots/​AzureInfrastructureExtensionsTests.BicepExtensions_GeneratesInlineDeclarations.verified.bicep Verifies extension declarations.
tests/​Aspire.Hosting.Azure.Tests/​Snapshots/​AzureInfrastructureExtensionsTests.BicepExtensions_EscapesReferenceLiterals.verified.bicep Verifies reference escaping.
tests/​Aspire.Hosting.Azure.Tests/​Snapshots/​AzureEnvironmentResourceTests.AzurePublishingContext_PreservesStructuredModuleParameters.verified.bicep Captures typed module parameters.
tests/​Aspire.Hosting.Azure.Tests/​AzureInfrastructureExtensionsTests.cs Tests extension generation and validation.
tests/​Aspire.Hosting.Azure.Tests/​AzureEnvironmentResourceTests.cs Tests structured publish parameters.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.Publish_SignInOnlyRegistration_RequiresExplicitIdTokenIssuance#01.verified.bicep Captures sign-in module.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.Publish_SignInOnlyRegistration_RequiresExplicitIdTokenIssuance#00.verified.bicep Captures sign-in parent deployment.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.Publish_CreatesSelfContainedGraphModulesAndOutputReferences#02.verified.bicep Captures consumer module.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.Publish_CreatesSelfContainedGraphModulesAndOutputReferences#01.verified.bicep Captures Graph module.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.Publish_CreatesSelfContainedGraphModulesAndOutputReferences#00.verified.bicep Captures composed deployment.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.ConfigureInfrastructure_CustomizesGraphResources.verified.bicep Captures infrastructure customization.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.AddEntraIdApplication_PublishesManifestWithParameters.verified.json Captures published manifest.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.AddEntraIdApplication_PublishesManifestWithParameters.verified.bicep Captures manifest module.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Snapshots/​EntraIdProvisioningTests.AddEntraIdApplication_GeneratesGraphBicep.verified.bicep Captures generated Graph resources.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​MicrosoftIdentityWebConfigurationTests.cs Verifies Identity.Web configuration.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​EntraIdValidationTests.cs Tests managed-identity validation.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​EntraIdResourceBuilderTests.cs Updates builder behavior tests.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​EntraIdProvisioningTests.cs Adds provisioning and publishing tests.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​EntraIdInitializationTests.cs Tests provisioning initialization outcomes.
tests/​Aspire.Hosting.Azure.EntraId.Tests/​Aspire.Hosting.Azure.EntraId.Tests.csproj Adds snapshot and Identity.Web dependencies.
src/​Aspire.Hosting.Azure/​AzureResourceInfrastructure.cs Exposes Bicep extension declarations.
src/​Aspire.Hosting.Azure/​AzurePublishingContext.cs Emits structured module parameters.
src/​Aspire.Hosting.Azure/​AzureProvisioningResource.cs Prepends inline extension declarations.
src/​Aspire.Hosting.Azure.EntraId/​README.md Documents provisioning and cleanup.
src/​Aspire.Hosting.Azure.EntraId/​Provisioning/​MicrosoftGraphServicePrincipal.cs Models Graph service principals.
src/​Aspire.Hosting.Azure.EntraId/​Provisioning/​MicrosoftGraphApplication.cs Models Graph applications.
src/​Aspire.Hosting.Azure.EntraId/​EntraIdValidation.cs Validates managed-identity client IDs.
src/​Aspire.Hosting.Azure.EntraId/​EntraIdResourceExtensions.cs Adds provisioning and existing-reference behavior.
src/​Aspire.Hosting.Azure.EntraId/​EntraIdApplicationResource.cs Implements the provisioning resource model.
src/​Aspire.Hosting.Azure.EntraId/​Aspire.Hosting.Azure.EntraId.csproj References Azure hosting infrastructure.
playground/​EntraId/​README.md Clarifies playground registration usage.
Directory.Packages.props Expands Identity.Web dependency scope.

🧠 Review effort: Balanced

Comment thread src/Aspire.Hosting.Azure.EntraId/EntraIdApplicationResource.cs Outdated
Comment thread src/Aspire.Hosting.Azure/AzurePublishingContext.cs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Keep native Boolean and integer values in manifests, include callback-returned dependencies, and evaluate module callbacks once before mapping their references and serializing their values. Reject unsupported values instead of silently emitting empty strings. Add focused regressions and document the deferred parameter contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Non-ARM Graph resources receive incompatible ARM commands, and the deployment path lacks repeatable end-to-end coverage.

1 open finding
2 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Add deployment E2E coverage for Graph Bicep extension

src/​Aspire.Hosting.Azure.EntraId/​EntraIdApplicationResource.cs:147

This adds real deployment behavior through the Microsoft Graph Bicep extension, but the committed coverage only validates generated Bicep/manifests; there is no deployment E2E test under tests/Aspire.Deployment.EndToEnd.Tests. A repeatable test should deploy the application and service principal, verify the outputs and stable update behavior, and clean up both directory objects. The manual validation in the PR description will not catch future extension/schema/auth regressions in CI.

🧠 Review effort: Balanced

Comment thread src/Aspire.Hosting.Azure.EntraId/EntraIdApplicationResource.cs
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Deferred structured values can produce string-typed generated module parameters and fail Bicep compilation.

2 open findings

🧠 Review effort: Balanced

Comment thread src/Aspire.Hosting.Azure/AzurePublishingContext.cs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Add optional Azure context naming suffix, preserve typed deferred generated parameters and secure references, and gate ARM management for directory resources.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60
@github-actions

This comment has been minimized.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Tenant-wide identity provisioning and shared Azure lifecycle changes require final human API and security review.

1 open finding
2 resolved since last review
Files not reviewed (1)
  • src/Aspire.Hosting.Azure/Resources/AzureProvisioningStrings.Designer.cs: Generated file

🧠 Review effort: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60
@github-actions

This comment has been minimized.

Preserve the fluent existing-registration API and add a top-level factory that does not enable Azure provisioning.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 68b00e03-418b-4839-88a1-d0ebfc318f60
@aspire-repo-bot
aspire-repo-bot Bot requested a balanced review from Copilot October 16, 2026 00:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The unresolved suffix-overwrite issue needs correction and regression coverage before final human review and approval.

1 open finding
Files not reviewed (1)
  • src/Aspire.Hosting.Azure/Resources/AzureProvisioningStrings.Designer.cs: Generated file

🧠 Review effort: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Tests selector

Selects the full PR test matrix + all PR-gated jobs (ALL) — a rule matching 'Directory.Packages.props' selects ALL


Selection computed for commit c41e812.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-integrations Issues pertaining to Aspire Integrations packages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants