This post shows how to implement a Swagger UI using a .NET 9 produced OpenAPI file. The Swagger UI is deployed to a secure or development environment and is not deployed to a public production target. Sometimes, it is required to deploy the Swagger UI to a development deployment target and not the test or the production deployments. The security headers need to be weakened to allow the Swagger UI to work.
Code: https://github.com/damienbod/WebApiOpenApi
Setup
The post uses the OpenAPI Json created by a .NET 9 ASP.NET Core application. See this blog:
Implementing an ASP.NET Core API with .NET 9 and OpenAPI
Two further packages were added to this project, one for the generation of the Swagger UI and the second package to generate the required security headers for an API using JWT Bearer tokens.
- Swashbuckle.AspNetCore.SwaggerUI
- NetEscapades.AspNetCore.SecurityHeaders
The ASP.NET Core API application has already generated the OpenAPI definitions in a Json file. The Json can be used to create the UI. If the application is deployed to production, the Json file is not created and the security headers are deployed with the most restrictions. If the application is deployed for development, the Json is deployed and the security headers are weakened to allow this to work.
// Open up security restrictions to allow this to work
// Not recommended in production
//var deploySwaggerUI = app.Environment.IsDevelopment();
var deploySwaggerUI = app.Configuration.GetValue<bool>("DeploySwaggerUI");
app.UseSecurityHeaders(
SecurityHeadersDefinitions.GetHeaderPolicyCollection(deploySwaggerUI));
// ... other middleware
app.MapOpenApi("/openapi/v1/openapi.json");
if (deploySwaggerUI)
{
app.UseSwaggerUI(options =>
{
options.SwaggerEndpoint("/openapi/v1/openapi.json", "v1");
});
}
The DeploySwaggerUI configuration is used to specify if the deployed version supports both a UI and an API or just an API with the most restrictive security settings.
{
// Open up security restrictions to allow this to work
// Not recommended in production
"DeploySwaggerUI": true,
Setup security headers
The security headers are setup so that if the deployment is for development, scripts and styles are allowed. The configuration allowing scripts is weak and not recommended for production.
namespace WebApiOpenApi;
public static class SecurityHeadersDefinitions
{
public static HeaderPolicyCollection GetHeaderPolicyCollection(bool isDev)
{
var policy = new HeaderPolicyCollection()
.AddFrameOptionsDeny()
.AddContentTypeOptionsNoSniff()
.AddReferrerPolicyStrictOriginWhenCrossOrigin()
.AddCrossOriginOpenerPolicy(builder => builder.SameOrigin())
.AddCrossOriginEmbedderPolicy(builder => builder.RequireCorp())
.AddCrossOriginResourcePolicy(builder => builder.SameOrigin())
.RemoveServerHeader()
.AddPermissionsPolicy(builder =>
{
builder.AddAccelerometer().None();
builder.AddAutoplay().None();
builder.AddCamera().None();
builder.AddEncryptedMedia().None();
builder.AddFullscreen().All();
builder.AddGeolocation().None();
builder.AddGyroscope().None();
builder.AddMagnetometer().None();
builder.AddMicrophone().None();
builder.AddMidi().None();
builder.AddPayment().None();
builder.AddPictureInPicture().None();
builder.AddSyncXHR().None();
builder.AddUsb().None();
});
AddCspHstsDefinitions(isDev, policy);
policy.ApplyDocumentHeadersToAllResponses();
return policy;
}
private static void AddCspHstsDefinitions(bool isDev, HeaderPolicyCollection policy)
{
if (!isDev)
{
policy.AddContentSecurityPolicy(builder =>
{
builder.AddObjectSrc().None();
builder.AddBlockAllMixedContent();
builder.AddImgSrc().None();
builder.AddFormAction().None();
builder.AddFontSrc().None();
builder.AddStyleSrc().None();
builder.AddScriptSrc().None();
builder.AddBaseUri().Self();
builder.AddFrameAncestors().None();
builder.AddCustomDirective("require-trusted-types-for", "'script'");
});
// maxage = one year in seconds
policy.AddStrictTransportSecurityMaxAgeIncludeSubDomains(maxAgeInSeconds: 60 * 60 * 24 * 365);
}
else
{
// allow swagger UI for dev
policy.AddContentSecurityPolicy(builder =>
{
builder.AddObjectSrc().None();
builder.AddBlockAllMixedContent();
builder.AddImgSrc().Self().From("data:");
builder.AddFormAction().Self();
builder.AddFontSrc().Self();
builder.AddStyleSrc().Self().UnsafeInline();
builder.AddScriptSrc().Self().UnsafeInline(); //.WithNonce();
builder.AddBaseUri().Self();
builder.AddFrameAncestors().None();
});
}
}
}
When the UI is deployed to in development mode, the Swagger UI is displayed and the user can enter a valid access token and use the APIs.

Notes
At present, the Swagger UI does not support script best practices and allowing this to work means deploying an unsecure web application with weak security. Any UI web application should use a strong CSP definition, for example like CSP nonces. An API has no UI and so should be locked down.
Links
https://github.com/martincostello/dotnet-minimal-api-integration-testing
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis/aspnetcore-openapi
https://learn.microsoft.com/en-us/aspnet/core/web-api/action-return-types