New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to restrict access to swagger/* folder? #384

Closed
gabriel-a opened this Issue Jun 10, 2015 · 29 comments

Comments

Projects
None yet
@gabriel-a

gabriel-a commented Jun 10, 2015

Greetings everyone,

I was wondering if someone found a way to restrict access to swagger/* folder, I tried DelegatingHandler as mentioned in #334 but I could not succeed. Also I tried to add location in web.config for swagger, it didn't work as well.

Anyone has any idea how to restrict access to documentation if the user is not authenticated?

@gabriel-a

This comment has been minimized.

Show comment
Hide comment
@gabriel-a

gabriel-a Jun 12, 2015

Found a pretty solution:

Created new folder: swagger
Added new Web.config file.

<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
</configuration>

If you have the authentication in MVC project, then the user have to be logged in to view the documentation.

Thanks:)

gabriel-a commented Jun 12, 2015

Found a pretty solution:

Created new folder: swagger
Added new Web.config file.

<configuration>
<system.web>
<authorization>
<deny users="?" />
</authorization>
</system.web>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
</configuration>

If you have the authentication in MVC project, then the user have to be logged in to view the documentation.

Thanks:)

@JohnGalt1717

This comment has been minimized.

Show comment
Hide comment
@JohnGalt1717

JohnGalt1717 Dec 18, 2015

Obviously this doesn't work if you're using OWIN or not using built in authentication. It would be really nice if there was a way to do the equivalent of [Authorize] at the top of the controller in a line of code in the config. Obviously using a Delegate handler is possible but it's a brute force approach to what should be a simple solution.

JohnGalt1717 commented Dec 18, 2015

Obviously this doesn't work if you're using OWIN or not using built in authentication. It would be really nice if there was a way to do the equivalent of [Authorize] at the top of the controller in a line of code in the config. Obviously using a Delegate handler is possible but it's a brute force approach to what should be a simple solution.

@jptrueblood

This comment has been minimized.

Show comment
Hide comment
@jptrueblood

jptrueblood Dec 22, 2015

Any solution? I am using OWIN, and am looking for a way to hide/secure the swagger ui from the general public, but am coming up short.

I also have to say, it took some doing to configure for OWIN, but once I had Swashbuckle up and running, I am amazed! Truly an incredibly useful utility for documenting and testing Web API implementations.

jptrueblood commented Dec 22, 2015

Any solution? I am using OWIN, and am looking for a way to hide/secure the swagger ui from the general public, but am coming up short.

I also have to say, it took some doing to configure for OWIN, but once I had Swashbuckle up and running, I am amazed! Truly an incredibly useful utility for documenting and testing Web API implementations.

@jptrueblood

This comment has been minimized.

Show comment
Hide comment
@jptrueblood

jptrueblood Dec 22, 2015

Thanks jreames9,

Great idea!

I had a similar thought, and will probably go with this solution in the short term.

However, it would be nice to have this functionality in production for troubleshooting, but this resource would definitely need to be a protected resource.

jptrueblood commented Dec 22, 2015

Thanks jreames9,

Great idea!

I had a similar thought, and will probably go with this solution in the short term.

However, it would be nice to have this functionality in production for troubleshooting, but this resource would definitely need to be a protected resource.

@mvarblow

This comment has been minimized.

Show comment
Hide comment
@mvarblow

mvarblow Apr 1, 2016

I see the issue is closed, but I don't see the solution for those of us running under OWIN. Did I miss it?

mvarblow commented Apr 1, 2016

I see the issue is closed, but I don't see the solution for those of us running under OWIN. Did I miss it?

@domaindrivendev

This comment has been minimized.

Show comment
Hide comment
@domaindrivendev

domaindrivendev Jul 7, 2016

Owner

As suggested - a DelegatingHandler is the easiest way to do this and should work with or without OWIN. See the example below which I've successfully tested with "Forms Authentication":

public class SwaggerAccessMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (IsSwagger(request) && !Thread.CurrentPrincipal.Identity.IsAuthenticated)
        {
            var response = request.CreateResponse(HttpStatusCode.Unauthorized);
            return Task.FromResult(response);
        }
        else
        {
            return base.SendAsync(request, cancellationToken);
        }
    }

    private bool IsSwagger(HttpRequestMessage request)
    {
        return request.RequestUri.PathAndQuery.StartsWith("/swagger");
    }
}

Wire up the handler in your SwaggeConfig.cs just before enabling Swagger as follows:

httpConfig.MessageHandlers.Add(new SwaggerAccessMessageHandler());

httpConfig.EnableSwagger(c =>
{
    ...
});
Owner

domaindrivendev commented Jul 7, 2016

As suggested - a DelegatingHandler is the easiest way to do this and should work with or without OWIN. See the example below which I've successfully tested with "Forms Authentication":

public class SwaggerAccessMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (IsSwagger(request) && !Thread.CurrentPrincipal.Identity.IsAuthenticated)
        {
            var response = request.CreateResponse(HttpStatusCode.Unauthorized);
            return Task.FromResult(response);
        }
        else
        {
            return base.SendAsync(request, cancellationToken);
        }
    }

    private bool IsSwagger(HttpRequestMessage request)
    {
        return request.RequestUri.PathAndQuery.StartsWith("/swagger");
    }
}

Wire up the handler in your SwaggeConfig.cs just before enabling Swagger as follows:

httpConfig.MessageHandlers.Add(new SwaggerAccessMessageHandler());

httpConfig.EnableSwagger(c =>
{
    ...
});
@figuerres

This comment has been minimized.

Show comment
Hide comment
@figuerres

figuerres Jul 8, 2016

thank you for the example and as soon as I can I will try it out in my setup and let you know if it works.
much appreciated !

figuerres commented Jul 8, 2016

thank you for the example and as soon as I can I will try it out in my setup and let you know if it works.
much appreciated !

@figuerres

This comment has been minimized.

Show comment
Hide comment
@figuerres

figuerres Jul 11, 2016

just tried this change and there is an issue I have.
to add the httpconfig inside the swaggerconfig.Register() method I need to pass in the httpconfiguration if this is to work like other .register() methods.
this throws a runtime error for me.

checking to see how to solve or if I made an error.

figuerres commented Jul 11, 2016

just tried this change and there is an issue I have.
to add the httpconfig inside the swaggerconfig.Register() method I need to pass in the httpconfiguration if this is to work like other .register() methods.
this throws a runtime error for me.

checking to see how to solve or if I made an error.

@figuerres

This comment has been minimized.

Show comment
Hide comment
@figuerres

figuerres Jul 11, 2016

Ahhh, ok the sample should read like this:
GlobalConfiguration.Configuration.MessageHandlers.Add(new SwaggerAccessMessageHandler());
not like this:
httpConfig.MessageHandlers.Add(new SwaggerAccessMessageHandler());

reason: the default swagger nugget package uses the "GlobalConfiguration.Configuration"
not "httpConfig"

I am now getting a 401 when I try to get the swagger folder.
I am using Identity Server V3 so now I just have to see how to get it to have me authenticate and i'll be good to go.

interestingly the swashbuckler / swagger setup is using Identity Server to allow access to the actual api calls in the swagger pages... now I just need to have it do that before I get to the swagger page.
may just need to setup a login page or something....

figuerres commented Jul 11, 2016

Ahhh, ok the sample should read like this:
GlobalConfiguration.Configuration.MessageHandlers.Add(new SwaggerAccessMessageHandler());
not like this:
httpConfig.MessageHandlers.Add(new SwaggerAccessMessageHandler());

reason: the default swagger nugget package uses the "GlobalConfiguration.Configuration"
not "httpConfig"

I am now getting a 401 when I try to get the swagger folder.
I am using Identity Server V3 so now I just have to see how to get it to have me authenticate and i'll be good to go.

interestingly the swashbuckler / swagger setup is using Identity Server to allow access to the actual api calls in the swagger pages... now I just need to have it do that before I get to the swagger page.
may just need to setup a login page or something....

@up2pixy

This comment has been minimized.

Show comment
Hide comment
@up2pixy

up2pixy Jul 25, 2016

@figuerres , have you get it setup successfully? In my case, the Thread.CurrentPrincipal.Identity.IsAuthenticated always return false..
I call the swagger UI like this:

HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/swagger/ui/index" }, WsFederationAuthenticationDefaults.AuthenticationType);

I also tried adding following part in Global.asax.cs but still not working...

protected void Application_PostAuthenticateRequest()
        {
            if (Request.IsAuthenticated)
            {
                Thread.CurrentPrincipal = HttpContext.Current.User;
            }
        }

Please help @domaindrivendev

up2pixy commented Jul 25, 2016

@figuerres , have you get it setup successfully? In my case, the Thread.CurrentPrincipal.Identity.IsAuthenticated always return false..
I call the swagger UI like this:

HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = "/swagger/ui/index" }, WsFederationAuthenticationDefaults.AuthenticationType);

I also tried adding following part in Global.asax.cs but still not working...

protected void Application_PostAuthenticateRequest()
        {
            if (Request.IsAuthenticated)
            {
                Thread.CurrentPrincipal = HttpContext.Current.User;
            }
        }

Please help @domaindrivendev

@mdhalgara

This comment has been minimized.

Show comment
Hide comment
@mdhalgara

mdhalgara Sep 26, 2016

@domaindrivendev - the DelegationHandler sample code you provided works for me. Thanks!
I am using IdentityServer3 + Asp.Net Identity on a Web API 2 solution.

mdhalgara commented Sep 26, 2016

@domaindrivendev - the DelegationHandler sample code you provided works for me. Thanks!
I am using IdentityServer3 + Asp.Net Identity on a Web API 2 solution.

@lolekjohn

This comment has been minimized.

Show comment
Hide comment
@lolekjohn

lolekjohn Oct 7, 2016

I figured out the way to do this. Use the latest swashbuckle version and add the below div tag in the injected index.html

<div id='auth_container'></div>

This will show an Authorize button in the swagger UI which can be used for authentication and once Authenticated, for all the requests to the API, the JWT token will be passed from the swagger UI

lolekjohn commented Oct 7, 2016

I figured out the way to do this. Use the latest swashbuckle version and add the below div tag in the injected index.html

<div id='auth_container'></div>

This will show an Authorize button in the swagger UI which can be used for authentication and once Authenticated, for all the requests to the API, the JWT token will be passed from the swagger UI

@figuerres

This comment has been minimized.

Show comment
Hide comment
@figuerres

figuerres Nov 27, 2016

Seems like the best path should be owin / katana as that is what Web api uses and does not get into the old Web forms and isapi mess. . Just my thought.

figuerres commented Nov 27, 2016

Seems like the best path should be owin / katana as that is what Web api uses and does not get into the old Web forms and isapi mess. . Just my thought.

@mguinness

This comment has been minimized.

Show comment
Hide comment
@mguinness

mguinness Jan 12, 2017

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();

mguinness commented Jan 12, 2017

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();
@cptndave

This comment has been minimized.

Show comment
Hide comment
@cptndave

cptndave Apr 17, 2017

@chadwackerman, sure it works, but installing Hexasoft.BasicAuthentication applies Basic Authentication across my site. I tried creating a swagger subdirectory with a web.config to enable this module only for swagger, but IIS gets in the way and when it sees a swagger directory it no longer invokes the swagger module and gives the "listing access denied" page instead of the swagger documentation. Therefore this doesn't look like a great solution unless there is another way to enable basic auth only for the swagger path.

cptndave commented Apr 17, 2017

@chadwackerman, sure it works, but installing Hexasoft.BasicAuthentication applies Basic Authentication across my site. I tried creating a swagger subdirectory with a web.config to enable this module only for swagger, but IIS gets in the way and when it sees a swagger directory it no longer invokes the swagger module and gives the "listing access denied" page instead of the swagger documentation. Therefore this doesn't look like a great solution unless there is another way to enable basic auth only for the swagger path.

@Structed

This comment has been minimized.

Show comment
Hide comment
@Structed

Structed Aug 22, 2017

Is there also a way to secure the API docs (eg /swagger) with BasicAuth, while the actual API requires JWT auth?
We have the situation where we secure the application with JWT via IdentityServer4, but want the API Docs to be independently secured.

Structed commented Aug 22, 2017

Is there also a way to secure the API docs (eg /swagger) with BasicAuth, while the actual API requires JWT auth?
We have the situation where we secure the application with JWT via IdentityServer4, but want the API Docs to be independently secured.

@sashafencyk

This comment has been minimized.

Show comment
Hide comment
@sashafencyk

sashafencyk Aug 29, 2017

@chadwackerman so, is there some right solution to protect subdirectory ? (with Basic Auth)

sashafencyk commented Aug 29, 2017

@chadwackerman so, is there some right solution to protect subdirectory ? (with Basic Auth)

@rwatjen

This comment has been minimized.

Show comment
Hide comment
@rwatjen

rwatjen Oct 3, 2017

@mguinness Thanks for that solution.

Additionally, if the site uses OpenIdConnect authentication, this line in the SwaggerAuthorizedMiddleware class:

context.Response.StatusCode = StatusCodes.Status401Unauthorized;

can successfully be replaced with

await context.ChallengeAsync();

This works by invoking the DefaultChallengeScheme configured with services.AddAuthentication in Startup.cs, and will trigger the OpenIdConnect login flow.

rwatjen commented Oct 3, 2017

@mguinness Thanks for that solution.

Additionally, if the site uses OpenIdConnect authentication, this line in the SwaggerAuthorizedMiddleware class:

context.Response.StatusCode = StatusCodes.Status401Unauthorized;

can successfully be replaced with

await context.ChallengeAsync();

This works by invoking the DefaultChallengeScheme configured with services.AddAuthentication in Startup.cs, and will trigger the OpenIdConnect login flow.

@mihaj

This comment has been minimized.

Show comment
Hide comment
@mihaj

mihaj Mar 22, 2018

@Structed I also want that. Any solutions?

mihaj commented Mar 22, 2018

@Structed I also want that. Any solutions?

@Structed

This comment has been minimized.

Show comment
Hide comment
@Structed

Structed Mar 22, 2018

@mihaj No, not really. We ended up turning off swagger docs in prod for now, until we open up the API to customers. We'll probably go a different route from there and have a central API gateway instead.

Structed commented Mar 22, 2018

@mihaj No, not really. We ended up turning off swagger docs in prod for now, until we open up the API to customers. We'll probably go a different route from there and have a central API gateway instead.

@mihaj

This comment has been minimized.

Show comment
Hide comment
@mihaj

mihaj Mar 24, 2018

I only need swagger in development/staging, but still would like to password protect it with minimal effort. The above solution is ok, but I need to create manual HTML to prompt the user to login to Oauth provider.

mihaj commented Mar 24, 2018

I only need swagger in development/staging, but still would like to password protect it with minimal effort. The above solution is ok, but I need to create manual HTML to prompt the user to login to Oauth provider.

@imxzjv

This comment has been minimized.

Show comment
Hide comment
@imxzjv

imxzjv Mar 27, 2018

I tried @mguinness solution but context.User.Identity.IsAuthenticated is always returning false for me :( (Core.All 2.05). Cookies are enabled, login is fine, other MVC pages show authenticated, token based requests authenticate. yeah. kinda lost.

imxzjv commented Mar 27, 2018

I tried @mguinness solution but context.User.Identity.IsAuthenticated is always returning false for me :( (Core.All 2.05). Cookies are enabled, login is fine, other MVC pages show authenticated, token based requests authenticate. yeah. kinda lost.

@mguinness

This comment has been minimized.

Show comment
Hide comment
@mguinness

mguinness Mar 27, 2018

@imxzjv The order of middleware is important, check that app.UseAuthentication() occurs before your swagger config.

mguinness commented Mar 27, 2018

@imxzjv The order of middleware is important, check that app.UseAuthentication() occurs before your swagger config.

@hengyiliu

This comment has been minimized.

Show comment
Hide comment
@hengyiliu

hengyiliu Apr 25, 2018

We have a Web API project which is secured by JwtBearer auth. I tried @mguinness solution, and User.Identity.IsAuthenticated is always false because the web app doesn't have a way to login.

Is there a way to configure WebAPI project to use JwtBearer auth for everything, but AzureAD/OpenIDConnect auth for /swagger path? I tried the following, but couldn't get it work. The error "No IAuthenticationSignInHandler is configured to handle sign in for the scheme: Bearer"

services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
 {
     options.Authority = "https://login.microsoftonline.com/...";
 })
.AddAzureAd(options =>
 {
     options.Instance = "https://login.microsoftonline.com/";
     // from https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore
 });

and

app.UseAuthentication();
app.UseSwaggerAuthorized();
app.UseSwagger();

hengyiliu commented Apr 25, 2018

We have a Web API project which is secured by JwtBearer auth. I tried @mguinness solution, and User.Identity.IsAuthenticated is always false because the web app doesn't have a way to login.

Is there a way to configure WebAPI project to use JwtBearer auth for everything, but AzureAD/OpenIDConnect auth for /swagger path? I tried the following, but couldn't get it work. The error "No IAuthenticationSignInHandler is configured to handle sign in for the scheme: Bearer"

services.AddAuthentication(options =>
{
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
 {
     options.Authority = "https://login.microsoftonline.com/...";
 })
.AddAzureAd(options =>
 {
     options.Instance = "https://login.microsoftonline.com/";
     // from https://github.com/Azure-Samples/active-directory-dotnet-webapp-openidconnect-aspnetcore
 });

and

app.UseAuthentication();
app.UseSwaggerAuthorized();
app.UseSwagger();
@Thwaitesy

This comment has been minimized.

Show comment
Hide comment
@Thwaitesy

Thwaitesy Aug 3, 2018

I have enhanced @mguinness solution to use a very simple Basic Auth for only the swagger paths. Basically we wanted the swagger stuff to be hidden in prod, unless you enter a known/shared username/password. This solution does just that, it pops up asking for auth details, which if correct lets you view the swagger stuff. - It also skips the authentication locally for dev.

I've copied the basic auth code from here: https://www.johanbostrom.se/blog/adding-basic-auth-to-your-mvc-application-in-dotnet-core

Please note - I haven't tested it with oAuth authentication turned on for swagger... this most likely will overwrite the basic auth header and stop you accessing swagger... You could probably enhance it then to also check if the request is authenticated via oAuth.. etc.

Anyways, its simple and gets the job done.

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>();
    }
}

Startup.cs

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

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

Thwaitesy commented Aug 3, 2018

I have enhanced @mguinness solution to use a very simple Basic Auth for only the swagger paths. Basically we wanted the swagger stuff to be hidden in prod, unless you enter a known/shared username/password. This solution does just that, it pops up asking for auth details, which if correct lets you view the swagger stuff. - It also skips the authentication locally for dev.

I've copied the basic auth code from here: https://www.johanbostrom.se/blog/adding-basic-auth-to-your-mvc-application-in-dotnet-core

Please note - I haven't tested it with oAuth authentication turned on for swagger... this most likely will overwrite the basic auth header and stop you accessing swagger... You could probably enhance it then to also check if the request is authenticated via oAuth.. etc.

Anyways, its simple and gets the job done.

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>();
    }
}

Startup.cs

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

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

This comment has been minimized.

Show comment
Hide comment
@bcpi

bcpi Aug 6, 2018

@Thwaitesy, thanks for the code. It seems to only work on Firefox. Keep getting auth prompts on Safari, Chrome, and Edge. Any ideas why?

-- update: seems to have been an issue with IIS setup. now working. thx

bcpi commented Aug 6, 2018

@Thwaitesy, thanks for the code. It seems to only work on Firefox. Keep getting auth prompts on Safari, Chrome, and Edge. Any ideas why?

-- update: seems to have been an issue with IIS setup. now working. thx

@Thwaitesy

This comment has been minimized.

Show comment
Hide comment
@Thwaitesy

Thwaitesy Aug 7, 2018

@bcpi id start by debugging the auth header check.. if its coming through there then I have no idea why its not working.. But if it's not coming through there then something is striping the auth header out of the request... I've only tested this in chrome, but will try others and see what the results are..

Thwaitesy commented Aug 7, 2018

@bcpi id start by debugging the auth header check.. if its coming through there then I have no idea why its not working.. But if it's not coming through there then something is striping the auth header out of the request... I've only tested this in chrome, but will try others and see what the results are..

@jsantanders

This comment has been minimized.

Show comment
Hide comment
@jsantanders

jsantanders Aug 11, 2018

Hi @Thwaitesy I tried your solution but I always get 401 Unauthorized.

jsantanders commented Aug 11, 2018

Hi @Thwaitesy I tried your solution but I always get 401 Unauthorized.

@Thwaitesy

This comment has been minimized.

Show comment
Hide comment
@Thwaitesy

Thwaitesy Aug 11, 2018

@jsantanders if you give me some more details I might be able to help?

It's been working great for us in all browsers....

Have you debugged it to see if its getting into the check login part? and its successful?

Outside of this, its possible some other auth is affecting the outcome.

Thwaitesy commented Aug 11, 2018

@jsantanders if you give me some more details I might be able to help?

It's been working great for us in all browsers....

Have you debugged it to see if its getting into the check login part? and its successful?

Outside of this, its possible some other auth is affecting the outcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment