I have an api build in .net core 2.1. To restrict access to various endpoints, I use IdentityServer4 and [Authorize] attribute. However, my goal during development is to expose the api swagger documentation to our developers so that they may use it no matter where they work from. The challenge that I face is how do I protect the swagger index.html file so that only they can see the details of the api.
I have created a custom index.html file in the wwwroot/swagger/ui folder and that all works, however, that file uses data from /swagger/v1/swagger.json
endpoint which is not protected. I would like to know how can I override the return value for that specific endpoint so that I may add my own authentication to it?
EDIT:
Currently, I have achieved the above with the following middleware:
public class SwaggerInterceptor
{
private readonly RequestDelegate _next;
public SwaggerInterceptor(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var uri = context.Request.Path.ToString();
if (uri.StartsWith("/swagger/ui/index.html"))
{
var param = context.Request.QueryString.Value;
if (!param.Equals("?key=123"))
{
context.Response.StatusCode = 404;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"result:\" \"Not Found\"}", Encoding.UTF8);
return;
}
}
await _next.Invoke(context);
}
}
public class Startup
{
//omitted code
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseMiddleware<SwaggerInterceptor>();
//omitted code
}
}
What I don't like about this approach as it will inspect every single request. Is there a better way to achieve this? The above only protects the index.html file, but I can adjust it to protect the json endpoint in the similar fashion.
You can choose some options: basic authorization. OpenId Connect authorization using identity server.
Add and configure Swagger middlewareLaunch the app and navigate to https://localhost:<port>/swagger/v1/swagger.json . The generated document describing the endpoints appears as shown in OpenAPI specification (openapi. json). The Swagger UI can be found at https://localhost:<port>/swagger .
To set up Swagger with Spring, we define it in a configuration bean. By default, this configuration bean is always injected into our Spring context. Thus, Swagger becomes available for all environments. To disable Swagger in production, let's toggle whether this configuration bean is injected.
Create an ASP.NET Core Web API project in Visual Studio 2022 Click on “Create new project.” In the “Create new project” window, select “ASP.NET Core Web API” from the list of templates displayed. Click Next. In the “Configure your new project” window, specify the name and location for the new project.
Here it is using OpenIdConnect and Swashbuckle in Asp.Net Core 3.1. Now, if I type https://myurl.com/swagger I get routed to my normal login page and after logging in successfully, I can see the swagger.
public class Startup
{
//<snip/>
public void Configure(IApplicationBuilder app)
{
//<snip/>
app.UseAuthentication();
app.UseAuthorization();
app.UseSwagger();
app.UseSwaggerUI(c => { c.SwaggerEndpoint("v1/swagger.json", "Some name"); });
app.UseEndpoints(routes =>
{
var pipeline = routes.CreateApplicationBuilder().Build();
routes.Map("/swagger", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
routes.Map("/swagger/index.html", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
routes.Map("/swagger/v1/swagger.json", pipeline).RequireAuthorization(new AuthorizeAttribute { AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme });
routes.Map("/swagger/{documentName}/swagger.json", pipeline).RequireAuthorization(new AuthorizeAttribute { AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme });
routes.MapDefaultControllerRoute();
});
}
}
EDIT:
Somehow I thought the below was working, but when I retested it later it turned out that actually it was giving error: The request reached the end of the pipeline without executing the endpoint. So, I changed to include a fixed set of endpoints under /swagger that contain afaik the key data.
routes.Map("/swagger/{**any}", pipeline).RequireAuthorization(new AuthorizeAttribute {AuthenticationSchemes = OpenIdConnectDefaults.AuthenticationScheme});
Note: this {**any} part of the route template protects all files under /swagger as well, so for example /swagger/index.html, /swagger/v1/swagger.json etc etc.
You can choose some options:
In this case you just close your swagger endpoints.
// Startup.cs
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication()
.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>("Basic", _ => {});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseEndpoints(endpoints =>
{
...
var pipeline = endpoints.CreateApplicationBuilder().Build();
var basicAuthAttr = new AuthorizeAttribute { AuthenticationSchemes = "Basic" };
endpoints
.Map("/swagger/{documentName}/swagger.json", pipeline)
.RequireAuthorization(basicAuthAttr);
endpoints
.Map("/swagger/index.html", pipeline)
.RequireAuthorization(basicAuthAttr);
});
}
}
// BasicAuthenticationHandler.cs
public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
{
...
}
I have written the article for this case: https://medium.com/dev-genius/csharp-protecting-swagger-endpoints-82ae5cfc7eb1
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With