Quick post today. MVC developers are used to the [RequireHttps] attribute, which is an authorization filter which doesn’t allow any requests to be made over "plain” HTTP. This attribute doesn’t exist in Web API, but it’s fairly simple to replicate the same behavior using an authorization filter (an action filter would work just as well, but since the MVC attribute is an IAuthorizationFilter, we did the same here). By having it as an attribute we can simply apply it to any controller or action, and all requests to that controller (or action) will pass through this attribute.

The logic is simple – if the request comes via HTTPS, nothing happens. If it doesn’t, then there are two possible alternatives: if the request verb is GET (or HEAD), then the filter bypasses the operation, and responds to the request with a redirect response, and most HTTP stacks will resend the request to the location specified. If the request verb is not GET (or HEAD), then we could also return a redirect response, but the problem is that many of the HTTP stacks (I don’t know if all of them, but I tried quite a few) issue a GET request in response to a Redirect from non-GET requests (since per the HTTP RFC they cannot send a non-GET request after redirecting without user confirmation), so that’s not what we want. In this case, we simply return an error response instead.

  1. public class RequireHttpsAttribute : AuthorizationFilterAttribute
  2. {
  3.     public override void OnAuthorization(HttpActionContext actionContext)
  4.     {
  5.         var request = actionContext.Request;
  6.         if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
  7.         {
  8.             HttpResponseMessage response;
  9.             UriBuilder uri = new UriBuilder(request.RequestUri);
  10.             uri.Scheme = Uri.UriSchemeHttps;
  11.             uri.Port = 443;
  12.             string body = string.Format("<p>The resource can be found at <a href=\"{0}\">{0}</a>.</p>",
  13.                 uri.Uri.AbsoluteUri);
  14.             if (request.Method.Equals(HttpMethod.Get) || request.Method.Equals(HttpMethod.Head))
  15.             {
  16.                 response = request.CreateResponse(HttpStatusCode.Found);
  17.                 response.Headers.Location = uri.Uri;
  18.                 if (request.Method.Equals(HttpMethod.Get))
  19.                 {
  20.                     response.Content = new StringContent(body, Encoding.UTF8, "text/html");
  21.                 }
  22.             }
  23.             else
  24.             {
  25.                 response = request.CreateResponse(HttpStatusCode.NotFound);
  26.                 response.Content = new StringContent(body, Encoding.UTF8, "text/html");
  27.             }
  28.  
  29.             actionContext.Response = response;
  30.         }
  31.     }
  32. }

That’s it. The behavior is almost identical to the MVC [RequireHttps] attribute, except that for non-GET requests the MVC attribute returns a 500 (Internal Server Error) response, while this implementation returns a 404 (Not Found) – thanks to @pmhsfelix for the insight on this.