To secure Swagger UI with Microsoft Entra ID, configure the browser client and the ASP.NET Core API as separate security boundaries. The reliable pattern is an OAuth 2.0 authorization code flow with PKCE, an exact redirect URI, a narrow delegated scope, and server-side validation of both the access token and that scope.

This guide rewrites the original October 2024 .NET 8 walkthrough around that production task. The original GitHub repository, portal screenshots, and result video remain available as historical evidence. The corrected snippets below are guidance derived from the documented platform behavior and the historical source; they are not presented as a fresh build or compatibility test against later package releases.

Separate the API from the Swagger client

Swagger UI runs in a browser. Anything delivered to that browser can be inspected, so Swagger must be treated as a public OAuth client. The API is a resource server: it accepts bearer tokens, validates them, and decides whether the caller has the required permission. These responsibilities should use two Microsoft Entra app registrations.

RegistrationPurposeImportant values
Protected APIExposes the resource and delegated permissionAPI client ID, Application ID URI, access_as_user scope
Swagger UI clientSigns in a user and requests the delegated permissionPublic client ID, exact OAuth redirect URI, API permission

Microsoft Entra ID authenticates the user and issues the access token. PKCE binds the authorization code to the browser session that started the flow. The API still has the final responsibility: it must reject missing, invalid, misdirected, or under-scoped tokens. A successful sign-in dialog alone proves none of those API checks.

Register and expose the protected API

Create the API registration first. Under Expose an API, set its Application ID URI and add a delegated scope such as access_as_user. With the default URI, the complete scope is typically api://YOUR_API_CLIENT_ID/access_as_user. Use a permission name and consent text that describe the operation rather than granting an unnecessarily broad capability.

Microsoft Entra ID screen exposing the API and creating the access_as_user scope
Historical 2024 portal screenshot: expose the API and create the delegated scope.

Keep token-validation settings separate from Swagger’s client settings. Use placeholders in public examples and inject environment-specific values through your normal configuration system.

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_API_CLIENT_ID",
    "Audience": "api://YOUR_API_CLIENT_ID",
    "Scopes": "access_as_user"
  }
}

Register Swagger UI as a public client

Create a second registration for Swagger UI. Add the callback used by Swagger’s OAuth helper as a redirect URI. For a default local Swagger route, it is commonly https://localhost:<port>/swagger/oauth2-redirect.html. The scheme, host, port, path, and slash behavior must match the registered value exactly.

Historical Microsoft Entra app registration for the Swagger UI client
Historical 2024 Swagger client registration.

Under API permissions, add the delegated access_as_user permission from the API registration. Apply user or admin consent according to the tenant’s policy. Do not create a client secret for browser-hosted Swagger UI. A value embedded in the page, generated JavaScript, or network request cannot remain confidential.

Adding the access_as_user delegated API permission to the Swagger UI client
Grant the Swagger client only the delegated API scope it needs.

Validate tokens and scopes in ASP.NET Core

The historical project targets .NET 8 and references Microsoft.Identity.Web. The package integrates bearer-token validation with Microsoft Entra configuration. Install it in a project that does not already reference it:

dotnet add package Microsoft.Identity.Web

Register authentication, add authorization, and place both middleware calls before mapped endpoints. The ordering makes authentication establish the principal before authorization evaluates access.

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

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

builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

[Authorize] rejects anonymous or invalid callers, but delegated authorization also requires the expected scope. [RequiredScope] checks the token’s scope claim before the action runs.

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;

[ApiController]
[Route("api/weather")]
[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public sealed class WeatherController : ControllerBase
{
    [HttpGet]
    public IActionResult Get() =>
        Ok(new { Status = "Protected" });
}

Describe the OAuth flow in OpenAPI

Swagger UI learns how to request a token from the OpenAPI security definition. Keep the client ID outside this document; the definition needs the tenant authorization endpoint, token endpoint, and full delegated scope.

{
  "SwaggerOAuth": {
    "TenantId": "YOUR_TENANT_ID",
    "ClientId": "YOUR_SWAGGER_CLIENT_ID",
    "ApiScope": "api://YOUR_API_CLIENT_ID/access_as_user"
  }
}
using Microsoft.OpenApi.Models;

var tenantId = builder.Configuration["SwaggerOAuth:TenantId"]!;
var apiScope = builder.Configuration["SwaggerOAuth:ApiScope"]!;

builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "Protected API",
        Version = "v1"
    });

    options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.OAuth2,
        Flows = new OpenApiOAuthFlows
        {
            AuthorizationCode = new OpenApiOAuthFlow
            {
                AuthorizationUrl = new Uri(
                    $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/authorize"),
                TokenUrl = new Uri(
                    $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token"),
                Scopes = new Dictionary<string, string>
                {
                    [apiScope] = "Access the API as the signed-in user"
                }
            }
        }
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        [new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference
            {
                Type = ReferenceType.SecurityScheme,
                Id = "oauth2"
            }
        }] = new[] { apiScope }
    });
});

Configure Swagger UI with PKCE

Initialize Swagger UI with the public client ID and enable PKCE. The scope separator is a space because Microsoft Entra expects a space-separated scope list.

app.UseSwagger();
app.UseSwaggerUI(options =>
{
    options.SwaggerEndpoint(
        "/swagger/v1/swagger.json",
        "Protected API v1");

    options.OAuthClientId(
        builder.Configuration["SwaggerOAuth:ClientId"]);
    options.OAuthUsePkce();
    options.OAuthScopeSeparator(" ");
});

The browser generates a code verifier, sends its derived challenge with the authorization request, and supplies the verifier when redeeming the returned code. PKCE protects the code exchange; it does not remove the API’s obligation to validate issuer, audience, signature, lifetime, and permission claims.

Correct the historical implementation gaps

The 2024 repository is intentionally preserved, including two details that should not be copied into a new implementation.

  1. Remove OAuthUseBasicAuthenticationWithAccessCodeGrant() for this public client. That option tells Swagger UI to use HTTP Basic client authentication during code redemption. It does not make a secret safe in a browser. Keep OAuthUsePkce(); omit both that Basic-auth option and OAuthClientSecret(...).
  2. Add app.UseAuthentication() before app.UseAuthorization(). The historical Program.cs registers authentication services but only calls the authorization middleware. The corrected pipeline above makes the intended token-validation step explicit.

These corrections update the explanation without rewriting history. The old repository and recording still show what was built in 2024; they do not certify a current deployment or support status.

Verify the security boundary

Verification should include deliberate failures, not only a successful Authorize dialog. Use a non-production tenant or test registrations, then record the HTTP status and the expected authorization reason for each case.

TestExpected resultWhat it proves
Call the protected action without a token401 UnauthorizedThe endpoint requires authentication.
Use a token for another audience or tenant401 UnauthorizedToken validation rejects a token for a different resource or issuer.
Use a valid delegated token without access_as_user403 ForbiddenThe API enforces the required scope.
Use a valid token with the required scopeThe endpoint’s normal success responseThe delegated path is correctly connected.
Change the callback to an unregistered URIMicrosoft Entra authorization errorThe response is bound to a registered redirect URI.
Swagger UI showing a 401 response from a protected ASP.NET Core API
A 401 response without a valid token is an expected control, not a defect.

When troubleshooting, inspect only the claims needed to diagnose the boundary: tenant, issuer, audience, expiry, and scp. Do not paste complete access tokens into tickets, screenshots, analytics, or shared logs. A decoded token is still a bearer credential until it expires.

Apply production controls

  • Do not expose interactive Swagger UI publicly by default. Restrict it by environment, network boundary, or an additional access policy.
  • Use HTTPS and register only the callback URIs required for each intended environment.
  • Request the smallest useful delegated scopes and enforce them at the API action or controller.
  • Keep client secrets, access tokens, and live credentials out of source code, configuration committed to Git, screenshots, and browser-delivered settings.
  • Review tenant restrictions and consent policy before external users can authorize the Swagger client.
  • Remove obsolete app registrations, redirect URIs, permissions, and credentials when the demo is retired.
  • Retest the complete success and failure matrix after framework, Swashbuckle, Microsoft.Identity.Web, or identity-platform changes.

This is a delegated, user-present flow. For an app-only service with no signed-in user, use the Microsoft Entra client credentials flow and validate application roles instead of delegated scopes.

Historical demo and source code

The following video records the original .NET 8 setup and result from October 2024. It demonstrates that historical environment; it is not a claim that the repository was rerun unchanged today.

Original Swagger and Microsoft Entra ID demonstration recorded in 2024.

Inspect the preserved implementation in the Swagger and Microsoft Entra ID repository. Treat identifiers in old screenshots or commits as historical. Do not restore or reuse old registrations or credentials. For a deeper explanation of the browser flow, read Microsoft Entra authorization code flow with PKCE in .NET.

References

Found this useful? Support more practical developer content.

Author

Practical .NET, Angular, Azure, Blazor, and AI engineering for real-world development.

Ads Blocker Image Powered by Code Help Pro

Ads Blocker Detected!!!

We have detected that you are using extensions to block ads. Please support us by disabling these ads blocker.

Powered By
Best Wordpress Adblock Detecting Plugin | CHP Adblock