How to switch between HTTP and HTTPS in ASP.NET MVC2

ASP.NET MVC2 has the new RequireHttpsAttribute that you can use to decorate any action to force any non-secure request to come through HTTPS (SSL).  It can be used as simply as this:

        [RequireHttps]
        public ActionResult LogOn()
        {
        .....
        }

Now any request to the LogOn action that is not over HTTPS will automatically be redirected back to itself over HTTPS.  This is very useful and easy to implement.

Unfortunately though, once the scheme is changed to HTTPS, all following requests will also be under HTTPS, which is not necessarily what you want.  In most cases you do not need all requests to your site to be secure but only certain ones such as the logon page or pages that accept credit card information. 

To handle this you can override the Controller.OnAuthorization method.  From within this method, you can check to see if the RequireHttps attribute is set on the Controller Action by searching the Attributes collection of the ActionDescriptor object. If the RequireHttpsAttribute is not set AND the request is under SSL, then return a redirect result to the HTTP (non-SSL) url:

public class ControllerBase : Controller
{

protected override void OnAuthorization(AuthorizationContext filterContext)

  //the RequireHttpsAttribute set on the Controller Action will handle redirecting to Https. 
  // We just need to handle any requests that are already under SSL but should not be. 
  if (Request.IsSecureConnection) 
   {
    Boolean requireHttps = false;
    requireHttps = filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Count() >= 1; 


    //If this request is under ssl but yet the controller action
    // does not require it, then redirect to the http version.
    if (!requireHttps && !filterContext.IsChildAction)
    {
        UriBuilder uriBuilder = new UriBuilder(Request.Url);

        //change the scheme
        uriBuilder.Scheme = "http";
        uriBuilder.Port = 80;

        filterContext.Result = this.Redirect(uriBuilder.Uri.AbsoluteUri);
    }
   }

    base.OnAuthorization(filterContext);
}

}

Now any HTTPS requests to controller actions that are not decorated with a RequireHttps attribute will be forced back to an HTTP (non-secure) request.

EDITED (2010-08-21): Modified the code as recommended by the comment from Donnie Hale to move the check for Request.SecureConnection to the outermost if block.

EDITED (2011-01-06): Modified the code as recommended by the comment from Sergio Tai to not perform this check if use Html.Action in views

17 Comments

  • Since the logic only needs executed if the page was requested via SSL, it makes sense to make "if (Request.IsSecureConnection) { ... }" the outermost conditional.

  • Nice solution - The other solution I've seen is to implement a [RequireHttp] counterpart. This works better for me though as I don't have to worry about littering all my non-ssl actions with the [RequireHttp] attribute.

    Thanks

  • Hey Jeff - Is there a reason you chose to override the OnAuthorize rather than (for example) the OnActionExecuted method of the base Controller?

  • Hi Kyle,
    OnAuthorization fires before your action is invoked and OnActionExecuted fires after.
    See http://msdn.microsoft.com/en-us/library/system.web.mvc.controller.onactionexecuted.aspx.
    So I want to redirect to SSL/non-SSL prior to the code in my action getting invoked so that is why to use the override of the OnAuthorization. I suppose
    overriding OnActionExecuting would have worked too but I believe OnAuthorization comes before OnActionExecuting (please check that though... I am not 100% sure) and since this is going to be a redirect I want to do this as early as possible.
    -Jeff

  • Awsome solution! I need this for jQuery AJAX calls as http/https are treated as separate domains and cross-domain ajax calls aren't support due to security issues.

    I had a change password page encrypted and after visiting that page, all my other ajax calls had issues unless it was redirected back to http.

  • This is a brilliant solution, however I am have a slight problem during implementation...

    requireHttps = filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Count >= 1;

    This line gives an error message...

    'System.Array' does not contain a definition for 'Count' and no extension method 'Count' accepting a first argument of type 'System.Array' could be found (are you missing a using directive or an assembly reference?)

    Any pointers? I copied the code above directly across.

    -Jake

  • Hi Jake,
    This is probably because you need to include the System.Linq namespace reference to get the Count extension method. Just add:
    "using System.Linq;"
    at the top of your .cs file to include the .Count extension method.
    -Jeff

  • Hi Jeff,

    I have implemented your solution but I get the following exception when accessing an action with [RequireHttps], "Child actions are not allowed to perform redirect actions". If I remove the override method all works well but with no conversion of request to http which I want. Any thoughts on the exception.

    /Kristoffer

  • Seems that the exception is caused by the Html.RenderAction that I use at some places in my Site.Master for different sections. I could get around it by using RenderPartial but some of the sections are handled by other Controllers.

  • protected override void OnAuthorization(AuthorizationContext filterContext)
    {

    //the RequireHttpsAttribute set on the Controller Action will handle redirecting to Https.
    // We just need to handle any requests that are already under SSL but should not be.

    if (Request.IsSecureConnection)
    {
    Boolean requireHttps = false;
    requireHttps = filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Count() >= 1;


    Boolean isChild = false;
    isChild = filterContext.ActionDescriptor.GetCustomAttributes(typeof(ChildActionOnlyAttribute), false).Count() >= 1;




    //If this request is under ssl but yet the controller action1
    // does not require it, then redirect to the http version.
    if (!requireHttps)
    {
    if (!isChild)
    {
    UriBuilder uriBuilder = new UriBuilder(Request.Url);

    //change the scheme
    uriBuilder.Scheme = "http";
    uriBuilder.Port = 80;

    filterContext.Result = this.Redirect(uriBuilder.Uri.AbsoluteUri);
    }
    }
    }

    base.OnAuthorization(filterContext);
    }

  • A fix to handle an exception that happens when you use Html.Action in views:


    protected override void OnAuthorization(AuthorizationContext filterContext)
    {

    //the RequireHttpsAttribute set on the Controller Action will handle redirecting to Https.
    // We just need to handle any requests that are already under SSL but should not be.
    if (Request.IsSecureConnection)
    {
    Boolean requireHttps = false;
    requireHttps = filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Count() >= 1;


    //If this request is under ssl but yet the controller action
    // does not require it, then redirect to the http version.
    if (!requireHttps && !filterContext.IsChildAction)
    {
    UriBuilder uriBuilder = new UriBuilder(Request.Url);

    //change the scheme
    uriBuilder.Scheme = "http";
    uriBuilder.Port = 80;

    filterContext.Result = this.Redirect(uriBuilder.Uri.AbsoluteUri);
    }
    }

    base.OnAuthorization(filterContext);
    }

  • Hi Sergio Tai,
    Thanks for the contribution. I updated code to include your recommendation.

    -Jeff

  • I had to change a line in order to get a controller where the RequireHttps attribute was defined at the class level (to require ssl for the entire controller):

    requireHttps =
    ((filterContext.ActionDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Count() >= 1) ||
    (filterContext.ActionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(RequireHttpsAttribute), false).Count() >= 1));

  • Great solution! It works like a charm!
    I only have one problem. On the particular page which I'm serving with HTTPS I have buttons which perform AJAX calls. They are placed in Site.Master which means I need to be able to be called through HTTP and HTTPS how would you go around that?

  • Hi Arturito,
    You should be able to indicate to the filter if the ajax call is through HTTP/HTTPS or to just ignore scheme altogether.
    -Jeff

  • your solution looks good, but I am wondering won't this approach will lead to an infinite loop of redirects????

  • Hi Neeraj,
    No, because the second redirect back to the same page (but just NOT under SSL) will not be caught by this filter; so it will pass through without a redirect.

    -Jeff

Comments have been disabled for this content.