Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to specify a different port with the RequireHttps attribute in MVC3

I am just learning ASP.NET MVC, and I have recently found the [RequireHttps] attribute to automatically redirect a GET request to use SSL, like so...

[RequireHttps] //apply to all actions in controller
public class SomeController 
{
    [RequireHttps] //apply to this action only
    public ActionResult SomeAction()
    {
        ...
    }
}

When using IIS Express as the development server, this successfully redirects the request from http://localhost:55945/... to https://localhost/....

However on my development system, my project is using HTTPS on port 44300 (this was configured automatically by Visual Studio 2010), and I have not yet found any way to tell MVC to use that port number in the redirection, so that it goes to the required https://localhost:43300/... instead.

I really expected this to be automatic, given the SSL port number was set automatically by Visual Studio, and as far as I can tell, this must be effecting all developers using the [RequireHttps] attribute in MVC3. In my searching for a solution, I have seen a few patchy "work around" solutions, but nothing that appears to be definitively "the right way" to fix it.

So doing things "the right way", what do I change (either in the source code, or in my project configuration) to tell the [RequireHttps] attribute in MVC3 to use the HTTPS port that my project is configured to be using?

Or alternatively, is there some other completely different and better "right way" to set up SSL support in an MVC3 project, that does not have this problem?

like image 608
Neville Cook Avatar asked Aug 18 '11 08:08

Neville Cook


3 Answers

The RequireHttpsAttribute is quite simple and it cannot be parametrized to redirect to a specific port. If you really need this you could create a subclass and override the HandleNonHttpsRequest method to compose the redirect url differently.

protected override void HandleNonHttpsRequest(AuthorizationContext filterContext) {
    base.HandleNonHttpsRequest(filterContext);

    // redirect to HTTPS version of page
    string url = "https://" + filterContext.HttpContext.Request.Url.Host + ":" + MyConfig.SslPort + filterContext.HttpContext.Request.RawUrl;
    filterContext.Result = new RedirectResult(url);
}

However, if your entire site runs with HTTPS you could just configure in VS in the web project properties (Web -> Start Action -> Start URL) to open the correct HTTPS url with your port and not use the redirect feature for local testing.

like image 170
Tz_ Avatar answered Oct 19 '22 21:10

Tz_


I have this same issue, and I'm solving it by using both the RequireHttpsAttribute and a URL rewrite rule in Web.config. The rewrite rule matches the nonstandard port numbers, and executes ahead of the attribute. You can use a Web.config transform to remove the rewrite rule on deployment, but if you leave it in it shouldn't have any effect. In production you'll use standard port number that the rule won't match. Then the attribute will catch it.

Here's the rule:

<system.webServer>
  <rewrite>
    <rules>
  <!-- Redirect HTTP requests to HTTPS, using the non-standard development ports.
  On deployment, this rule is removed, and the RequireHttpAttribute filter globally applied
  in SlicerWeb.FilterConfig takes over. This rewrite rule executes before the attribute
  would be applied, and so can apply the port numbers -->
      <rule name="HTTPS redirect" stopProcessing="true">
        <match url="(.*)" />
        <conditions>
          <add input="{HTTPS}" pattern="off" ignoreCase="true" />
          <add input="{SERVER_PORT}" pattern="60470" />
        </conditions>
        <action type="Redirect" url="https://{SERVER_NAME}:44300/{R:1}" redirectType="Found" />
      </rule>
    </rules>
  </rewrite>
</system.webServer>

The downside to this approach is that you're not relying on the attribute when running locally. But if you've applied it once globally, instead of adding it to every controller, that's OK in my opinion.

like image 22
Carl Raymond Avatar answered Oct 19 '22 19:10

Carl Raymond


A few things that might be helpful.

There is a version of the source code of RequireHttpsAttribute in this thread: Where is the source for RequireHttpsAttribute?

There is also a nearly identical class called RequireSslAttribute on codeplex mentioned on the same thread. http://aspnet.codeplex.com/SourceControl/changeset/view/63930#391756

Here is a sample of an attribute that can be used to toggle either from http to https or the reverse based on the TargetUriScheme property. It also includes properties for specifying port numbers.

I have chosen to use an #if DEBUG block in my constructor to set my local development ports when I am building under the Debug configuration. This works for me since I always build under release when deploying, in which case the port numbers will default to null and will be left out of the url's.

The port numbers can also be set when applying the attribute to an action method. I could also see hooking these to a config file or some other configuration source to determine the port numbers at runtime.

public class ToggleHttpHttpsAttribute : FilterAttribute, IAuthorizationFilter
{
    //supported uri scheme values
    public enum UriScheme
    {
        Http,
        Https
    }

    public ToggleHttpHttpsAttribute(
        UriScheme uriScheme = UriScheme.Http)
    {
        TargetUriScheme = uriScheme;
    #if DEBUG
        //set DEBUG ports
        HttpPort = 55892;
        HttpsPort = 44301;
    #endif
    }

    private UriScheme TargetUriScheme { get; set; }
    public int? HttpPort { get; set; }
    public int? HttpsPort { get; set; }

    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if(filterContext == null)
        {
            throw new ArgumentNullException("filterContext");
        }

        bool isHttps = filterContext.HttpContext.Request.IsSecureConnection;

        if ((isHttps && TargetUriScheme == UriScheme.Http) || (!isHttps && TargetUriScheme == UriScheme.Https))
        {
            ToggleUriScheme(filterContext);
        }
    }

    private void ToggleUriScheme(AuthorizationContext filterContext)
    {
        //only allow toggle if GET request
        if (!string.Equals(filterContext.HttpContext.Request.HttpMethod, "GET",   StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("ToggleHttpHttpsAttribute can only be used on GET requests.");
        }

        filterContext.Result = GetRedirectResult(filterContext);
    }

    private RedirectResult GetRedirectResult(AuthorizationContext filterContext)
    {
        string prefix = TargetUriScheme == UriScheme.Http
            ? "http://"
            : "https://";

        int? port = TargetUriScheme == UriScheme.Http
            ? HttpPort
            : HttpsPort;

        string url = string.Format(
            "{0}{1}{2}{3}",
            prefix,
            filterContext.HttpContext.Request.Url.Host,
            port == null ? "" : string.Format(":{0}", port),
            filterContext.HttpContext.Request.RawUrl);

        return new RedirectResult(url);
    }
}
like image 2
bingles Avatar answered Oct 19 '22 20:10

bingles