Skip to content

[Entra ID] Add protected Web API authentication integration #3853

Description

@danroth27

Summary

Add Entra ID support for ASP.NET Core Web APIs, including controller-based and Minimal API endpoints.

The current scaffolder accepts Web API projects but applies web-app OpenID Connect and Blazor-specific code. It reports success and leaves the generated API unable to build. If the Blazor artifact is manually removed, unauthenticated API requests receive browser login redirects instead of JWT bearer challenges.

Web API support must compose with MVC, Razor Pages, Razor components, SignalR, and other capabilities hosted by the same ASP.NET Core application.

Related: #2890

Steps to reproduce

Minimal API

dotnet new webapi -n EntraMinimalApi `
    --framework net11.0

dotnet scaffold aspnet entra-id `
    --project .\EntraMinimalApi\EntraMinimalApi.csproj `
    --username <tenant-user> `
    --tenantId <tenant> `
    --use-existing-application false

dotnet build .\EntraMinimalApi\EntraMinimalApi.csproj

Controller API

dotnet new webapi -n EntraControllerApi `
    --framework net11.0 `
    --use-controllers

dotnet scaffold aspnet entra-id `
    --project .\EntraControllerApi\EntraControllerApi.csproj `
    --username <tenant-user> `
    --tenantId <tenant> `
    --use-existing-application false

dotnet build .\EntraControllerApi\EntraControllerApi.csproj

Current behavior

The scaffolder configures both API shapes as interactive web applications:

builder.Services.AddAuthentication(
        OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(
        builder.Configuration.GetSection("AzureAd"));

builder.Services.AddCascadingAuthenticationState();

builder.Services.AddAuthorization(options =>
{
    options.FallbackPolicy = options.DefaultPolicy;
});

It also generates:

Components/Layout/LoginOrLogout.razor

Both Web API projects fail to build because the generated Blazor component depends on imports and types that aren't present:

warning RZ10012: Found markup element with unexpected name
'AuthorizeView'.

error CS0246: The type or namespace name
'LocationChangedEventArgs' could not be found.

If the generated Components directory is manually removed, the APIs build but still have incorrect authentication behavior:

  • AddMicrosoftIdentityWebApp configures cookie/OIDC web sign-in instead of bearer-token validation.
  • AddMicrosoftIdentityWebApi isn't generated.
  • The default authentication scheme is OpenID Connect instead of JWT bearer.
  • No delegated scope or application-role validation is generated.
  • Minimal API endpoints don't call RequireAuthorization or validate an accepted scope.
  • API controllers don't receive [Authorize] or [RequiredScope].
  • A request without a bearer token receives an HTTP 302 redirect to login.microsoftonline.com instead of an API 401 response.
  • A password credential and Web redirect URIs are created even though a protected API doesn't need interactive sign-in.

Expected behavior

When API capabilities are detected, generate protected Web API integration appropriate to the endpoint style.

Pure Web API host

For an application that only hosts APIs, configure JWT bearer authentication:

builder.Services.AddAuthentication(
        JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(
        builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization();

Don't generate:

AddMicrosoftIdentityWebApp(...)
AddCascadingAuthenticationState()
MapLoginAndLogout()

Don't generate login/logout UI.

A protected API registration doesn't require Web redirect URIs or a client credential unless the API also acts as a confidential client to call another API.

Minimal API endpoints

Protect selected API endpoints and validate the required delegated scope:

var requiredScope =
    app.Configuration["AzureAd:Scopes"];

app.MapGet("/weatherforecast", (HttpContext context) =>
{
    context.VerifyUserHasAnyAcceptedScope(requiredScope);

    // ...
})
.RequireAuthorization();

An equivalent authorization policy or RequireScope extension is also appropriate.

The scaffolder should allow the user to select or configure the exposed scope, with a default such as:

access_as_user

Controller APIs

Generate controller authorization:

[Authorize]
[RequiredScope(
    RequiredScopesConfigurationKey = "AzureAd:Scopes")]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    // ...
}

Prefer a convention, global filter, or policy when protecting all API controllers, rather than editing every controller independently.

App-registration provisioning

For a new protected API registration, the scaffolder should:

  • Create an Application ID URI, such as api://<client-id>.

  • Expose a delegated scope such as access_as_user.

  • Support defining application roles for app-only callers.

  • Avoid enabling implicit grants.

  • Avoid adding Web redirect URIs unless the same registration also represents an interactive web client.

  • Avoid creating a client credential unless the API calls another API.

  • Generate matching configuration:

    {
      "AzureAd": {
        "Instance": "https://login.microsoftonline.com/",
        "TenantId": "<tenant-id>",
        "ClientId": "<api-client-id>",
        "Audience": "api://<api-client-id>",
        "Scopes": "access_as_user"
      }
    }

Endpoints that accept application tokens should validate app roles instead of delegated scopes. Endpoints that accept both should use the corresponding Microsoft Identity Web scope-or-app-permission support.

HTTP behavior

Generated APIs should return:

  • 401 Unauthorized for a missing or invalid token.
  • 403 Forbidden for an authenticated token that lacks the required scope or role.

They shouldn't redirect API clients to a browser login page.

Capability detection and composition

Detect independent API capabilities instead of assigning one exclusive project type.

Possible capability model:

public sealed class AspNetAppCapabilities
{
    public bool HasMvcViews { get; init; }
    public bool HasRazorPages { get; init; }
    public bool HasRazorComponents { get; init; }
    public bool HasControllerApis { get; init; }
    public bool HasMinimalApis { get; init; }
    public bool HasSignalR { get; init; }
}

Controller API capabilities can be detected from:

builder.Services.AddControllers();
app.MapControllers();

plus controller semantics such as:

[ApiController]
ControllerBase

AddControllersWithViews() may host both MVC views and API controllers, so controller discovery should inspect the controllers rather than treating MVC and APIs as mutually exclusive.

Minimal API capabilities can be detected from endpoint registrations such as:

app.MapGet(...);
app.MapPost(...);
app.MapGroup(...);

In a mixed UI/API host, not every mapped endpoint is necessarily an API requiring bearer authentication. The scaffolder should identify endpoint groups that are already marked as APIs, use existing authorization metadata, or prompt the user to select which route groups/endpoints to protect.

Capability combinations should compose:

  • UI + controller API: retain cookie/OIDC as the UI default and add a named JWT bearer scheme/policy for API controllers.
  • UI + Minimal API: apply the API bearer policy only to selected endpoint groups.
  • Blazor Web App + API/BFF endpoints: keep tokens server-side and authorize the server endpoints independently.
  • API + SignalR: add the SignalR bearer-token transport handling without replacing ordinary API validation.

For a mixed host, don't globally replace the UI authentication scheme with JWT bearer. Configure named schemes or a policy scheme and apply the appropriate authorization policy to each surface.

All registrations, packages, and endpoint changes must be idempotent.

Template parity

The generated pure Web API result should preserve and modernize the Microsoft Identity Platform behavior currently provided by:

dotnet new webapi --auth SingleOrg

Graph, downstream API, and OBO support are tracked separately by #3847 or follow-up API-specific work.

Relevant areas

  • ValidateEntraIdStep
  • ASP.NET Core capability detection/model
  • Web API app-registration provisioning
  • Controller API code modifications
  • Minimal API code modifications
  • API scope/role options and configuration

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions