File: EntraIdRunModeBrowserOrigin.cs
Web Access
Project: src\src\Aspire.Hosting.Azure.EntraId\Aspire.Hosting.Azure.EntraId.csproj (Aspire.Hosting.Azure.EntraId)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
 
namespace Aspire.Hosting.Azure;
 
/// <summary>
/// Keeps an endpoint's primary origin deferred until the registration's Bicep is generated.
/// </summary>
internal sealed record EntraIdEndpointRedirectUri(Func<ReferenceExpression> Origin, string Path);
 
/// <summary>
/// Bounds the wait for browser URLs when provisioning an Entra redirect URI.
/// </summary>
internal sealed class EntraIdRunModeBrowserOrigin(
    EndpointReference endpoint,
    DistributedApplicationExecutionContext executionContext) : IExpressionValue, IValueWithReferences
{
    public string ValueExpression => endpoint.PrimaryBrowserOrigin.ValueExpression;
 
    public IEnumerable<object> References => [endpoint];
 
    public ValueTask<string?> GetValueAsync(CancellationToken cancellationToken = default) =>
        GetValueAsync(new(), cancellationToken);
 
    public async ValueTask<string?> GetValueAsync(ValueProviderContext context, CancellationToken cancellationToken = default)
    {
        // Preserve the existing Entra wait boundary: allocation (which can include a slow
        // build) is not part of the 30-second budget for URL customization.
        var allocatedUrl = await endpoint.Property(EndpointProperty.Url).GetValueAsync(context, cancellationToken).ConfigureAwait(false);
        using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        timeout.CancelAfter(TimeSpan.FromSeconds(30));
        try
        {
            return await endpoint.PrimaryBrowserOrigin.GetValueAsync(context, timeout.Token).ConfigureAwait(false);
        }
        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
        {
            // Slow or indirectly dependent callbacks previously used the allocated URL.
            // Keep that compatibility fallback local to Entra, not general browser-origin resolution.
            var origin = new Uri(allocatedUrl!, UriKind.Absolute).GetLeftPart(UriPartial.Authority);
            executionContext.Services.GetRequiredService<ResourceLoggerService>().GetLogger(endpoint.Resource).LogWarning(
                "Browser URL finalization for endpoint '{EndpointName}' on resource '{ResourceName}' did not complete within 30 seconds. " +
                "Using allocated origin '{Origin}' for the Entra redirect URI. This may differ from a custom browser URL; configure an explicit redirect URI if necessary.",
                endpoint.EndpointName, endpoint.Resource.Name, origin);
            return origin;
        }
    }
}