Sitemap
Any Programming

Practical software engineering across every stack from C# and .NET to Python, TypeScript, Go, and Rust. Hands-on tutorials, system design, design patterns, DevOps, and AI-assisted development, with working code you can actually run. By developers, for developers.

Swagger Security in .NET 8 API

Enhancing Security with Basic Authentication

4 min readMar 25, 2025

--

Press enter or click to view image in full size
Swagger Security in .NET 8 API

If you create a .NET 8 API project in Visual Studio, Swagger is available by default. When using Swagger, you will consider about security. You can set the Swagger documentation to be visible only in the internal network. However, anyone who knows the domain internally can view the Swagger documentation through the /swagger path. You can handle this issue by using basic authentication to enhance security and control API exposure.

Senario

Here is a Swagger document with APIs called Product and User.

Press enter or click to view image in full size
default
  1. I will create SwaggerAuthMiddleware.cs and secure it with Basic Authentication.
  2. In the sample code, I will use only two accounts: admin and user.
    And when accessing by user account, Swagger will hide the Product and only expose the User.

*The sample source code hard-codes the data for convenience.

Source Code

SwaggerAuthMiddleware.cs

public class SwaggerAuthMiddleware
{
private readonly RequestDelegate _next;

public SwaggerAuthMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context)
{
// Check the swagger document path
if (!context.Request.Path.StartsWithSegments("/swagger"))
{
await _next(context);
return;
}

// Authentication and role verification
string? role = GetUserRole(context);
if (string.IsNullOrEmpty(role))
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"Swagger UI\"";
return;
}

// Call FilterSwaggerJsonResponse if role is not an admin
if (IsSwaggerJsonRequest(context) && !role.Equals("admin", StringComparison.OrdinalIgnoreCase))
{
await FilterSwaggerJsonResponse(context, role);
return;
}

await _next(context);
}

/// <summary>
/// Basic Authentication and Assigning Roles to Accounts
/// </summary>
private string? GetUserRole(HttpContext context)
{
var authHeader = context.Request.Headers["Authorization"].ToString();
if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Basic "))
{
var encodedCredentials = authHeader.Substring("Basic ".Length).Trim();
try
{
var decodedBytes = Convert.FromBase64String(encodedCredentials);
var decodedCredentials = Encoding.UTF8.GetString(decodedBytes);
var credential = decodedCredentials.Split(':', 2);
if (credential.Length == 2)
{
var username = credential[0];
var password = credential[1];

// Verify account and grant roles
if (username == "admin" && password == "0000")
{
return "admin";
}
else if (username == "user" && password == "1234")
{
return "user";
}
}
}
catch
{
Console.WriteLine("error!");
}
}
return null;
}

/// <summary>
/// Check SwaggerJson Request
/// </summary>
private bool IsSwaggerJsonRequest(HttpContext context)
{
return context.Request.Path.Value?.Contains("swagger.json", StringComparison.OrdinalIgnoreCase) == true;
}

/// <summary>
/// Return a Swagger JSON response filtered by role
/// </summary>
private async Task FilterSwaggerJsonResponse(HttpContext context, string role)
{
var allowedPrefixes = GetAllowedPathForRole(role);
if (allowedPrefixes == null || !allowedPrefixes.Any())
{
await _next(context);
return;
}

var originalBodyStream = context.Response.Body;
using (var memStream = new MemoryStream())
{
context.Response.Body = memStream;
await _next(context);
memStream.Position = 0;

string json = await new StreamReader(memStream).ReadToEndAsync();
var swaggerDoc = JsonSerializer.Deserialize<Dictionary<string, object>>(json);

// Verify that the parsed Swagger document is valid and contains a "paths" key
if (swaggerDoc != null && swaggerDoc.ContainsKey("paths"))
{
var pathsJson = swaggerDoc["paths"].ToString();
var paths = JsonSerializer.Deserialize<Dictionary<string, object>>(pathsJson);
if (paths != null)
{
// Filter paths based on allowed path
var filteredPaths = paths
.Where(p => allowedPrefixes.Any(prefix => p.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)))
.ToDictionary(p => p.Key, p => p.Value);

// Reassign filtered paths to "paths" in the Swagger document
swaggerDoc["paths"] = filteredPaths;
json = JsonSerializer.Serialize(swaggerDoc);
}
}

context.Response.Body = originalBodyStream;
context.Response.ContentLength = Encoding.UTF8.GetByteCount(json);
await context.Response.WriteAsync(json);
}
}

/// <summary>
/// Get role's allowed API paths
/// </summary>
private List<string> GetAllowedPathForRole(string role)
{
var allowedPaths = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase)
{
{ "user", new List<string> { "/api/v1/user" } }
};

return allowedPaths.ContainsKey(role) ? allowedPaths[role] : [];
}
}

Invoke(HttpContext context)
1. Check Swagger path
2. If role does not exist, return 401 Unauthorized response
3. Check if request is swagger.json request
4. If role is not admin, process path filtering

Get bcal’s stories in your inbox

Join Medium for free to get updates from this writer.

GetUserRole(HttpContext context)
1. Get authentication information from Authorization header.
2. Check if it is Basic authentication
3. Decode authentication information ( username:password )
4. Check username and password and return role

IsSwaggerJsonRequest(HttpContext context)
1.
Check if the request path contains swagger.json

FilterSwaggerJsonResponse(HttpContext context, string role)
1. Get the allowed API paths based on the role
2. Read the saved JSON data and parse it into a Swagger document
3. Filter only the paths starting with the allowed path prefix
4. Serialize the filtered paths and reassign filtered paths to the response and return.

GetAllowedPathForRole(string role)
1. Returns the paths allowed by role

Program.cs

...

var app = builder.Build();

// Place the Middleware before the UseSwagger() method.
app.UseMiddleware<SwaggerAuthMiddleware>();

app.UseSwagger();
app.UseSwaggerUI(c =>
{
...

Execution

When accessing the Swagger document, a form for entering ID/PW appears.
Entered user account information.

Basic Authentication

User account is filtered and only User API is exposed in Swagger.

Press enter or click to view image in full size
User account filtered

Conclusion

If you want to enhance Swagger security, try to use Basic Authentication and Swagger Path filters. It can be used more usefully than you might think.

--

bcal
bcal

Written by bcal

Between Coding And Life | Fullstack Developer | .NET, C#, AWS, and lessons from real-world projects.

Any Programming
Any Programming

Published in Any Programming

Practical software engineering across every stack from C# and .NET to Python, TypeScript, Go, and Rust. Hands-on tutorials, system design, design patterns, DevOps, and AI-assisted development, with working code you can actually run. By developers, for developers.