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

Published 19 August 2010 10:00 PM by Jeff Widmer
Filed under: , , , ,

Comments

# Twitter Trackbacks for How to switch between HTTP and HTTPS in ASP.NET MVC2 - Jeff Widmer's Blog [asp.net] on Topsy.com said on 20 August, 2010 04:36 AM

Pingback from  Twitter Trackbacks for                 How to switch between HTTP and HTTPS in ASP.NET MVC2 - Jeff Widmer's Blog         [asp.net]        on Topsy.com

# Robert Milne said on 20 August, 2010 06:06 AM

Nice solution, I did something similar with an custom attribute, blogged about it at fearofaflatplanet.me.uk/.../switching-between-http-and-https-automatically-with-asp-net-mvc

# Donnie Hale said on 20 August, 2010 04:48 PM

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

# Stephen Garside said on 03 September, 2010 11:13 AM

This is great stuff. I am just writing an online shop in MVC2 (we also have another one in web forms) and this is so much easier in MVC2. We want to be able to just SSL our checkout pages so popping this attribute on the relevant controller methods has done the trick.  We also want the user to drop back to http on the non-ssl pages, which your suggestion does really well - Many thanks!

The RequireHttps attribute also works well on pages that are https that make ajax calls back to controller methods sporting these attributes - happy days :)

# Pritesh said on 16 September, 2010 07:01 AM

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

# Kyle said on 20 October, 2010 11:49 AM

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

# Jeff Widmer said on 21 October, 2010 01:22 PM

Hi Kyle,

OnAuthorization fires before your action is invoked and OnActionExecuted fires after.  

See msdn.microsoft.com/.../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

# Murray Bauer said on 02 November, 2010 04:09 AM

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.

# Jake said on 03 November, 2010 11:50 AM

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

# Jeff Widmer said on 05 November, 2010 11:11 AM

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

# Kristoffer said on 11 November, 2010 08:26 AM

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

# Kristoffer said on 11 November, 2010 11:53 AM

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.

# Bogus said on 26 November, 2010 06:14 AM

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);

           }

# Sergio Tai said on 06 January, 2011 02:57 PM

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);

}

# Jeff Widmer said on 06 January, 2011 03:08 PM

Hi Sergio Tai,

Thanks for the contribution.  I updated code to include your recommendation.

-Jeff

# Keith said on 22 February, 2011 02:33 PM

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));

# arturito said on 12 April, 2011 11:57 AM

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?

# Jeff Widmer said on 18 April, 2011 11:57 AM

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

Leave a Comment

(required) 
(required) 
(optional)
(required) 

Search

Go

This Blog

News

Syndication