ASP.NET Core's project templates dropped Swashbuckle a couple of major versions back in favor of native OpenAPI document generation. That's a good change — fewer third-party moving parts — but it means the old "just install Swashbuckle and go" instructions for securing your docs with OIDC are out of date. Here's the current way to do it in .NET 10.
- ASP.NET Core generates the OpenAPI document natively via
AddOpenApi()/MapOpenApi()— Swashbuckle or Scalar is only needed for the interactive UI on top of it. - Document transformers are how you add a security scheme to the generated OpenAPI JSON so Swagger UI shows an Authorize button.
- Protecting the endpoints themselves is a normal
[Authorize]+ JWT bearer/OIDC concern — it's separate from documenting that they're protected. - Don't expose interactive API docs in production without authentication in front of them, even if every endpoint they describe is itself protected.
Generating the OpenAPI document
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.MapOpenApi(); // serves /openapi/v1.json
app.MapSwaggerUi(); // Swagger UI, or use Scalar.AspNetCore for a modern alternative
}
This gets you a spec-compliant OpenAPI document with zero third-party dependencies. For the interactive UI, either add the Swashbuckle.AspNetCore.SwaggerUI package pointed at the generated document, or use Scalar.AspNetCore, which has become the more common choice for new projects — both just need the OpenAPI JSON URL, not a separate document generator.
Adding a JWT/OIDC security scheme to the document
By default, the generated OpenAPI document doesn't know your API requires a bearer token. A document transformer adds the security scheme definition so the docs UI can show an Authorize button and attach the token to test requests.
public class BearerSecuritySchemeTransformer : IOpenApiDocumentTransformer
{
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken ct)
{
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes["oidc"] = new OpenApiSecurityScheme
{
Type = SecuritySchemeType.OpenIdConnect,
OpenIdConnectUrl = new Uri("https://your-identity-provider/.well-known/openid-configuration"),
};
document.SecurityRequirements.Add(new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme { Reference = new OpenApiReference
{ Id = "oidc", Type = ReferenceType.SecurityScheme } }] = []
});
return Task.CompletedTask;
}
}
// Program.cs
builder.Services.AddOpenApi(options =>
options.AddDocumentTransformer<BearerSecuritySchemeTransformer>());
Protecting the endpoints
The security scheme in the docs is just metadata — the actual protection is ordinary JWT bearer authentication validated against your identity provider's OIDC metadata.
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-identity-provider";
options.Audience = "orders-api";
options.TokenValidationParameters.ValidateIssuer = true;
});
builder.Services.AddAuthorization();
// ...
app.MapGet("/orders/{id}", GetOrder).RequireAuthorization();
Letting testers log in from the docs UI
To make the Authorize button actually usable — not just decorative — configure the docs UI with your identity provider's authorization endpoint and a public client that supports the Authorization Code flow with PKCE. This lets a developer click Authorize, log in through your real OIDC provider, and have the resulting token attached to every request they try from the docs.
app.MapScalarApiReference(options =>
{
options.WithOAuth2Authentication(auth =>
{
auth.ClientId = "swagger-ui-client";
auth.AuthorizationUrl = "https://your-identity-provider/connect/authorize";
auth.TokenUrl = "https://your-identity-provider/connect/token";
auth.UsePkce = true;
});
});
Keep it out of production, or gate it hard
Interactive API docs are a reconnaissance tool for anyone who finds them, protected endpoints or not — they enumerate your entire surface area in one place. The pattern above wraps MapOpenApi()/MapScalarApiReference() in an IsDevelopment() check for a reason. If you need docs in staging or production, put them behind their own authentication middleware or an internal-only network route, separate from the API's own auth.