azureazure-mobile-servicesswaggerswagger-ui

Swagger authentication in Azure App Service


In my Azure Mobile .NET backend I want to use Azure Mobile .NET Server Swagger . I'm looking for fast way to hide swagger UI from public access ? Is there any way to provide access only for selected users ?


Solution

  • First a disclaimer: Even if you protect your Swagger UI from public consumption, you are not protecting your APIs from public consumption. You have to assume that everyone knows all of your routes and have the appropriate security in place to protect any requests that may come in.

    That being said, there's still not a simple way to do this. Swashbuckle (the piece that adds Swagger to Web API) adds a custom HttpMessageHandler to the /swagger/ui route (as seen here). If you look at the Web API pipeline, you can see that if you specify a custom handler, you can bypass all of the Controller selection, Auth filters, etc. This is what happens here.

    Some solutions:

    1. Use an app setting to conditionally call ConfigureSwagger(config) in debug modes only. This would prevent all /swagger routes from making it into production. Or you could use a staging slot and only add it there.
    2. You can wrap the SwaggerUiHandler with something like this Basic Auth MessageHandler. This would prompt the user for basic creds if they went to the /swagger/ui route. See below for my modified version of this code.

    Maybe with a little more thought we can come up with a better solution -- I see a couple of issues (here and here) in the Swashbuckle repo that indicate you're not the first one to hit this.

    Modified BasicAuthHandler (from here):

    Warning: minimally tested (and be sure to change how you verify user/pass)

    public class BasicAuthMessageHandler : DelegatingHandler
    {
        private const string BasicAuthResponseHeader = "WWW-Authenticate";
        private const string BasicAuthResponseHeaderValue = "Basic";
    
        public BasicAuthMessageHandler(HttpMessageHandler innerHandler)
        {
            this.InnerHandler = innerHandler;
        }
    
        protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
        {
            AuthenticationHeaderValue authValue = request.Headers.Authorization;
            HttpResponseMessage unauthorizedResponse = request.CreateUnauthorizedResponse();
    
            if (authValue != null && !string.IsNullOrWhiteSpace(authValue.Parameter))
            {
                Credentials parsedCredentials = ParseAuthorizationHeader(authValue.Parameter);
                if (parsedCredentials != null)
                {
                    // TODO: Check that the user/pass are valid
                    if (parsedCredentials.Username == "user" &&
                        parsedCredentials.Password == "pass")
                    {
                        // If match, pass along to the inner handler
                        return base.SendAsync(request, cancellationToken);
                    }
                }
            }
            else
            {
                // Prompt for creds
                unauthorizedResponse.Headers.Add(BasicAuthResponseHeader, BasicAuthResponseHeaderValue);
            }
    
            return Task.FromResult(unauthorizedResponse);
        }
    
        private Credentials ParseAuthorizationHeader(string authHeader)
        {
            string[] credentials = Encoding.ASCII.GetString(Convert
                                                            .FromBase64String(authHeader))
                                                            .Split(
                                                            new[] { ':' });
            if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0])
                || string.IsNullOrEmpty(credentials[1])) return null;
            return new Credentials()
            {
                Username = credentials[0],
                Password = credentials[1],
            };
        }
    }
    

    Registering with Swagger route

    // Do this after calling ConfigureSwagger
    ConfigureSwagger(config);
    
    // Remove the swagger_ui route and re-add it with the wrapped handler.
    var route = config.Routes["swagger_ui"];
    config.Routes.Remove("swagger_ui");
    config.Routes.MapHttpRoute("swagger_ui", route.RouteTemplate, route.Defaults, route.Constraints, new BasicAuthMessageHandler(route.Handler));