So, .NET 9 showed up and decided Swashbuckle’s time in the templates was over.
No AddSwaggerGen(), no UseSwagger(), just this new built-in OpenAPI thing that looks minimal and kind of confusing the first time you see it.
But here’s the thing — Swagger UI isn’t gone.
Microsoft just separated the parts. The framework now generates OpenAPI docs by itself, and you can still pull in Swashbuckle.AspNetCore.SwaggerUI to display them.
Basically, you get a native OpenAPI backend with the same good-looking Swagger UI frontend. Lighter, cleaner, faster.
Let’s walk through how that works with a real setup that adds JWT Bearer support and shows you a working /swagger endpoint.
first, the code that wires it all together:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerUI;
namespace MyApi.Extensions
{
public static class OpenAPIConfigurationExtension
{
public static void AddOpenApiConfiguration(this IServiceCollection services)
{
services.AddOpenApi(options =>
{
options.AddDocumentTransformer<BearerSecuritySchemeTransformer>();
});
}
public static void UseOpenAPIWithSwagger(this WebApplication app)
{
app.MapOpenApi();
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1.json", "v1");
options.EnablePersistAuthorization();
options.DisplayRequestDuration();
options.EnableTryItOutByDefault();
options.EnableFilter();
options.DocExpansion(DocExpansion.List);
options.DefaultModelsExpandDepth(0);
});
}
private static readonly OpenApiSecurityScheme BearerSecurityScheme = new()
{
Type = SecuritySchemeType.Http,
Scheme = JwtBearerDefaults.AuthenticationScheme,
In = ParameterLocation.Header,
BearerFormat = "Json Web Token"
};
internal sealed class BearerSecuritySchemeTransformer(
IAuthenticationSchemeProvider authenticationSchemeProvider
) : IOpenApiDocumentTransformer
{
private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider = authenticationSchemeProvider
?? throw new ArgumentNullException(nameof(authenticationSchemeProvider));
private bool? _hasBearerScheme;
public async Task TransformAsync(
OpenApiDocument document,
OpenApiDocumentTransformerContext context,
CancellationToken cancellationToken
)
{
if (_hasBearerScheme.HasValue && !_hasBearerScheme.Value)
return;
if (!_hasBearerScheme.HasValue)
{
var authenticationSchemes = await _authenticationSchemeProvider.GetAllSchemesAsync();
_hasBearerScheme = authenticationSchemes.Any(scheme => scheme.Name == JwtBearerDefaults.AuthenticationScheme);
if (!_hasBearerScheme.Value)
return;
}
document.Components ??= new OpenApiComponents();
document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>(1);
document.Components.SecuritySchemes[JwtBearerDefaults.AuthenticationScheme] = BearerSecurityScheme;
var securityRequirement = new OpenApiSecurityRequirement
{
[new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = JwtBearerDefaults.AuthenticationScheme,
Type = ReferenceType.SecurityScheme
}
}] = Array.Empty<string>()
};
foreach (var path in document.Paths.Values)
{
foreach (var operation in path.Operations.Values)
{
operation.Security ??= new List<OpenApiSecurityRequirement>(1);
operation.Security.Add(securityRequirement);
}
}
}
}
}
}
that’s the whole setup — AddOpenApiConfiguration adds Microsoft’s built-in OpenAPI generator, and the transformer handles JWT auth. the UseOpenAPIWithSwagger bit hooks up Swagger UI as a viewer for /openapi/v1.json.
you don’t need AddSwaggerGen, you don’t need Swashbuckle filters. this is pure OpenAPI + a thin Swagger UI frontend.
here’s the minimal Program.cs to make it work:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using MyApi.Extensions;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new()
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "myapi",
ValidAudience = "myapi-users",
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes("supersecretkey123")
)
};
});
builder.Services.AddOpenApiConfiguration();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
app.UseOpenAPIWithSwagger();
app.MapControllers();
app.Run();
run that, and you’ll have:
-
/openapi/v1.json— the new built-in OpenAPI doc -
/swagger— Swagger UI reading from it
JWT bearer auth shows up automatically in the top-right authorize dialog, and tokens are persisted between calls.
no Swashbuckle reflection, no extra annotations, just a clean and modern setup.
what you actually need in your project file is just:
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.4" />
</ItemGroup>
and that’s it.
So yeah, Swashbuckle the package might be out, but Swagger UI isn’t dead.
you can keep your interactive docs, lose the heavy reflection layer, and let .NET handle OpenAPI generation natively.
feels like the right direction — leaner, simpler, and still developer-friendly.
Top comments (0)