Hamza Khanzada
Hamza Khanzada

Reputation: 1529

Global Authorize filter not working with Swagger UI Asp.net Web Api

I am implementing Swagger UI with my Asp.net WEB Api project, I am using the default System.Web.Http.AuthorizeAttribute, I have registered it in my WebApiConfig.cs in Register method as

 config.Filters.Add(new AuthorizeAttribute());

I have implemented Swagger UI as

public static void Register()
{
 var thisAssembly = typeof(SwaggerConfig).Assembly;

 GlobalConfiguration.Configuration
 .EnableSwagger(c =>
 {
    c.SingleApiVersion("v1", "COE.Services.WebAPI");

    c.OAuth2("oauth2")
    .Description("OAuth2 Implicit Grant")
    .Flow("implicit")
    .AuthorizationUrl(configurationService.BaseWithTokenUrl)
    .Scopes(scopes =>
    {
        scopes.Add("user_scope", "Access REST API");
    });

    c.OperationFilter<AssignOAuth2SecurityRequirements>();
})
.EnableSwaggerUi(c =>
{
    c.EnableOAuth2Support("COEApi", configurationService.BaseUrl + "swagger/ui/o2c-html", "Swagger");
});
}     

 public class AssignOAuth2SecurityRequirements : IOperationFilter
 {
    public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription)
    {
        var toBeAuthorize = apiDescription.GetControllerAndActionAttributes<AuthorizeAttribute>().Any();
        var allowAnonymous = apiDescription.ActionDescriptor.GetCustomAttributes<AllowAnonymousAttribute>().Any();

        if (toBeAuthorize && !allowAnonymous)
        {
            if (operation.parameters == null)
                operation.parameters = new List<Parameter>();

            operation.parameters.Add(new Parameter()
            {
                name = "Authorization",
                @in = "header",
                description = "Bearer <token>",
                required = true,
                type = "string"
            });
        }
    }
}

I have also tried to search the solution on Swashbuckle's Git hub repository but I couldn't find any solution.

I have also come across on opened issue about this on Github

Upvotes: 2

Views: 2459

Answers (1)

Mariusz Pawelski
Mariusz Pawelski

Reputation: 28842

Swagger API and UI in Swashbuckle are implemented as HttpMessageHandler for route. So filters doesn't work there (because they work only for ASP.NET WebAPI's controllers and actions).

However, you can protect from unathorized access to swagger differently. If you use ASP.NET Web API as Owin middleware (by use of Microsoft.AspNet.WebApi.Owin nuget package) then you can write simple middleware and put it before swagger middleware to reject requests to "swagger/docs/" and "swagger/ui/" (default routes for Swagger API and UI).

For example:

[assembly: OwinStartupAttribute(typeof(WebApplicationMvcWebApiSwagger.Startup))]
namespace WebApplicationMvcWebApiSwagger
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);

            app.UseRejectUnathorizedUsersForSwagger();

            var config = new HttpConfiguration();

            config
                .EnableSwagger(c =>
                {
                    c.SingleApiVersion("v1", "A title for your API");
                })
                .EnableSwaggerUi();

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
            app.UseWebApi(config);
        }
    }

    public static class SwaggerAuthorizationMiddleware
    {
        public static void UseRejectUnathorizedUsersForSwagger(this IAppBuilder appBuilder)
        {
            appBuilder.Use((context, next) =>
            {
                if (context.Request.Path.Value.StartsWith("/swagger/docs/")
                    || context.Request.Path.Value.StartsWith("/swagger/ui/"))
                {
                    var user = (context.Request.User as ClaimsPrincipal);
                    if (user == null || !user.Identity.IsAuthenticated)
                    {
                        context.Response.StatusCode = 401;
                        context.Response.ContentType = "text/plain";
                        return context.Response.WriteAsync("Unauthorized. Log in to use swagger.");
                    }
                }

                return next.Invoke();
            });
        }
    }
}

ConfigureAuth(app) is a middleware responsible for authentication (here generated by Visual Studio template which uses ASP.NET Identity for it). After auth middleware and before WebApi middleware (which adds also swagger) you can put you own middleware with custom authentication logic.

If you don't use Owin for ASP.NET API then you can try to implement HttpMessageHandler and add there similar logic like in previous OWIN middleware example. You should be able to use Thread.CurrentPrincipal to get authorization data (or HttpContext.Current.User when hosting in IIS? I'm not sure about that):

protected void Application_Start()
{
    //...
    GlobalConfiguration.Configure(WebApiConfig.Register);
    //...
}
config.MessageHandlers.Add(new RequireAdminUserForSwaggerDocAndUiHandler());

config
    .EnableSwagger(c =>
    {
        c.SingleApiVersion("v1", "A title for your API");
    })
    .EnableSwaggerUi();

config.MapHttpAttributeRoutes();

config.Routes.MapHttpRoute(
    name: "DefaultApi",
    routeTemplate: "api/{controller}/{id}",
    defaults: new { id = RouteParameter.Optional }
);
public class RequireAdminUserForSwaggerDocAndUiHandler : DelegatingHandler
{
    async protected override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (request.RequestUri.PathAndQuery.StartsWith("/swagger/docs/")
            || request.RequestUri.PathAndQuery.StartsWith("/swagger/ui/"))
        {
            if (Thread.CurrentPrincipal == null || !Thread.CurrentPrincipal.Identity.IsAuthenticated)
            {
                var response = new HttpResponseMessage();
                response.StatusCode = System.Net.HttpStatusCode.Unauthorized;
                response.Content = new StringContent("Unauthorized. Log in to use swagger.");
                return response;
            }
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

Upvotes: 1

Related Questions