Joshua Leigh
Joshua Leigh

Reputation: 360

Secure swagger docs page with username and password Asp Net Core 2.1

I am using Asp.Net Core 2.1 Web Api with Swashbuckle.aspnetcore.swagger

I want to secure api documentation page with username and password before granting access.

Sample documention page enter image description here

To make sure its not accessible by the public

Upvotes: 25

Views: 20306

Answers (3)

CoffeeCode
CoffeeCode

Reputation: 1

If you are trying to clear cache and headers after closing browser.

Here is what you can add:

public async Task InvokeAsync(HttpContext context)
{
    //Make sure we are hitting the swagger path, and not doing it locally as it just gets annoying :-)
    if (context.Request.Path.StartsWithSegments("/swagger") && !this.IsLocalRequest(context))
    {
        string authHeader = context.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.StartsWith("Basic "))
        {
            // Get the encoded username and password
            var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();

            // Decode from Base64 to string
            var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));

            // Split username and password
            var username = decodedUsernamePassword.Split(':', 2)[0];
            var password = decodedUsernamePassword.Split(':', 2)[1];

            //remove header from cache
            context.Request.Headers.Remove("Authorization");
            context.Response.GetTypedHeaders().CacheControl =
            new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
            {
                Public = false,
                MaxAge = TimeSpan.FromSeconds(0)
            };

            // Check if login is correct
            if (IsAuthorized(username, password))
            {
                await next.Invoke(context);
                return;
            }
        }

        // Return authentication type (causes browser to show login dialog)
        context.Response.Headers["WWW-Authenticate"] = "Basic";

        // Return unauthorized
        context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
    }
    else
    {
        await next.Invoke(context);
    }
}

Upvotes: 0

Gautam Sharma
Gautam Sharma

Reputation: 1267

I have found a solution on GitHub and applied it to my project. its working as expected.

Below code copied from https://github.com/domaindrivendev/Swashbuckle.WebApi/issues/384#issuecomment-410117400

public class SwaggerBasicAuthMiddleware
{
    
private readonly RequestDelegate next;

    public SwaggerBasicAuthMiddleware(RequestDelegate next)
    {
        this.next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        //Make sure we are hitting the swagger path, and not doing it locally as it just gets annoying :-)
        if (context.Request.Path.StartsWithSegments("/swagger") && !this.IsLocalRequest(context))
        {
            string authHeader = context.Request.Headers["Authorization"];
            if (authHeader != null && authHeader.StartsWith("Basic "))
            {
                // Get the encoded username and password
                var encodedUsernamePassword = authHeader.Split(' ', 2, StringSplitOptions.RemoveEmptyEntries)[1]?.Trim();

                // Decode from Base64 to string
                var decodedUsernamePassword = Encoding.UTF8.GetString(Convert.FromBase64String(encodedUsernamePassword));

                // Split username and password
                var username = decodedUsernamePassword.Split(':', 2)[0];
                var password = decodedUsernamePassword.Split(':', 2)[1];

                // Check if login is correct
                if (IsAuthorized(username, password))
                {
                    await next.Invoke(context);
                    return;
                }
            }

            // Return authentication type (causes browser to show login dialog)
            context.Response.Headers["WWW-Authenticate"] = "Basic";

            // Return unauthorized
            context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
        }
        else
        {
            await next.Invoke(context);
        }
    }

    public bool IsAuthorized(string username, string password)
    {
        // Check that username and password are correct
        return username.Equals("SpecialUser", StringComparison.InvariantCultureIgnoreCase)
                && password.Equals("SpecialPassword1");
    }

    public bool IsLocalRequest(HttpContext context)
    {
        //Handle running using the Microsoft.AspNetCore.TestHost and the site being run entirely locally in memory without an actual TCP/IP connection
        if (context.Connection.RemoteIpAddress == null && context.Connection.LocalIpAddress == null)
        {
            return true;
        }
        if (context.Connection.RemoteIpAddress.Equals(context.Connection.LocalIpAddress))
        {
            return true;
        }
        if (IPAddress.IsLoopback(context.Connection.RemoteIpAddress))
        {
            return true;
        }
        return false;
    }
}
public static class SwaggerAuthorizeExtensions
    {
        public static IApplicationBuilder UseSwaggerAuthorized(this IApplicationBuilder builder)
        {
            return builder.UseMiddleware<SwaggerBasicAuthMiddleware>();
        }
    }

In Startup.cs

app.UseAuthentication(); //Ensure this like is above the swagger stuff

app.UseSwaggerAuthorized();
app.UseSwagger();
app.UseSwaggerUI();

Upvotes: 20

Shaul Behr
Shaul Behr

Reputation: 38091

Copied from mguinness's answer on Github:


In .NET Core you use middleware, instead of a DelegatingHandler:

public class SwaggerAuthorizedMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task Invoke(HttpContext context)
    {
        if (context.Request.Path.StartsWithSegments("/swagger")
            && !context.User.Identity.IsAuthenticated)
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }

        await _next.Invoke(context);
    }
}

You will also need an extension method to help adding to pipeline:

public static class SwaggerAuthorizeExtensions
{
    public static IApplicationBuilder UseSwaggerAuthorized(this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<SwaggerAuthorizedMiddleware>();
    }
}

Then add to Configure method in Startup.cs just before using Swagger:

app.UseSwaggerAuthorized();
app.UseSwagger();
app.UseSwaggerUi();

There's also a variant solution posted there how to do it with basic auth.

Upvotes: 17

Related Questions