TrueWill
TrueWill

Reputation: 25543

Enforcing SSL in service with reverse proxy

Based on the article Working with SSL in Web API I implemented an authorization filter to require SSL for a method of a Web API (2.1) Controller:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true,
                AllowMultiple = false)]
public sealed class RequireHttpsAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.RequestUri.Scheme != Uri.UriSchemeHttps)
        {
            actionContext.Response = new HttpResponseMessage(HttpStatusCode.Forbidden)
                                         {
                                             ReasonPhrase = "HTTPS Required"
                                         };
        }
        else
        {
            base.OnAuthorization(actionContext);
        }
    }
}

This works fine - on some web servers. If Web Farm Framework (WFF) is used as a reverse proxy, it can fail (by blocking valid HTTPS requests).

WFF adds the header X-Forwarded-Proto, which is a de facto standard for reverse proxies.

How can I revise this code to work with or without a standard proxy?

Upvotes: 1

Views: 804

Answers (1)

TrueWill
TrueWill

Reputation: 25543

Here's what I came up with:

/// <summary>
/// Action filter to require SSL for a protected resource.
/// </summary>
/// <remarks>
/// From http://www.asp.net/web-api/overview/security/working-with-ssl-in-web-api
/// but modified to support reverse proxies such as Web Farm Framework.
/// </remarks>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
public sealed class RequireHttpsAttribute : AuthorizationFilterAttribute
{
    [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Not possible.")]
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (IsSecure(actionContext.Request))
        {
            base.OnAuthorization(actionContext);
        }
        else
        {
            actionContext.Response =
                new HttpResponseMessage(HttpStatusCode.Forbidden)
                    {
                        ReasonPhrase = "HTTPS Required"
                    };
        }
    }

    private static bool IsSecure(HttpRequestMessage request)
    {
        if (request.RequestUri.Scheme == Uri.UriSchemeHttps)
        {
            return true;
        }

        IEnumerable<string> headerValues;

        if (request.Headers.TryGetValues("X-Forwarded-Proto", out headerValues))
        {
            string protocol = headerValues.FirstOrDefault();

            return string.Equals(protocol, "https", StringComparison.OrdinalIgnoreCase);
        }

        return false;
    }
}

Upvotes: 1

Related Questions