MVC RequireHttps entire site - asp.net-mvc

I have read the previous posts about using the RequireHttpsAttribute to secure individual controllers:
ASP.NET MVC RequireHttps in Production Only
but is there a way to apply this to the entire site? Due to my host (discountasp.net) I cannot use the "RequireSSL IIS" setting.

Register the RequireHttpsAttribute as a global filter.
In global.asax:
protected void Application_Start()
{
GlobalFilters.Filters.Add(new RequireHttpsAttribute());
//... other stuff
}

I ended up using IIS URL Rewrite 2.0 to force the site to switch to HTTPS. This code in web.config does the trick:
<system.webServer>
<!-- This uses URL Rewrite 2.0 to force the entire site into SSL mode -->
<rewrite xdt:Transform="Insert">
<rules>
<rule name="Force HTTPS" enabled="true">
<match url="(.*)" ignoreCase="false" />
<conditions>
<add input="{HTTPS}" pattern="off" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>

You could always add a check at the application level in your global.asax
protected void Application_BeginRequest(Object sender, EventArgs e)
{
if (!HttpContext.Current.Request.IsSecureConnection)
{
Response.Redirect("https://" + Request.ServerVariables["HTTP_HOST"]
+ HttpContext.Current.Request.RawUrl);
}
}

Just to bring this answer upto date for MVC 3 and above use the following in your Filterconfig.cs file within the App_start folder
filters.Add(new RequireHttpsAttribute());
Obviously you will need your servers IIS configured to use a valid SSL certificate, cheap certs can be purchased here: https://www.namecheap.com/ i think the last time i purchased one it was $9 per domain per year.

In your FilterConfig.cs apply this:
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
// only if Debug is not enabled, do not require https for local development
if (!HttpContext.Current.IsDebuggingEnabled)
filters.Add(new RequireHttpsAttribute());
//... any other filters
}
That should force your app to use https on every page.

This isn't using RequireHttps but I think it's a better solution because it catches the redirect sooner in the MVC Lifecycle.
public class RedirectModule : IHttpModule
{
private HttpApplication _context;
public void Init(HttpApplication context)
{
_context = context;
_context.PostResolveRequestCache += HttpRedirect;
}
public void HttpRedirect(Object src, EventArgs args)
{
if (_context.Request.Url.Scheme == Uri.UriSchemeHttp)
{
//Redirect to https
var scheme = Uri.UriSchemeHttps + "://";
var authority = _context.Request.Url.Authority;
var url = _context.Request.RawUrl;
var redirectTo = scheme + authority + url;
_context.Response.PermanentRedirect(redirectTo);
}
}
public void Dispose() { }
}
The idea came from this article.
You can register the module in your Web.config or inside the Global.asax. I'll show you in the web.cofig.
<system.webServer>
<modules>
<add name="ConfigModuleName" type="Your.Namespace.RedirectModule"/>
</modules>
</system.webServer>

MVC 6 (ASP.NET Core 1.0) works slightly different in it's way of registering filters:
Startup.cs - AddMvc with filter for RequireHttpsAttribute:
public void ConfigureServices(IServiceCollection services)
{
// TODO: Register other services
services.AddMvc(options =>
{
options.Filters.Add(typeof(RequireHttpsAttribute));
});
}
Design decisions explained:
Use filter in Startup.cs for global setup (since we want this to apply everywhere). Startup should be responsible for registering and setting up all global rules. If your company employ a new developer, she would expect to find global setup in Startup.cs.
Use RequireHttpsAttribute logic since it's proven (by Microsoft). Never use "magical" strings like "http://" and "https://" when it can be avoided by reusing a Microsoft component created to provide the same logic.
If you are running your MVC website in localhost without SSL:
http://localhost:1337/ (no SSL)
https://localhost:1337/ (SSL)
Consider looking at how to run without SSL in localhost while still requiring https it in production.
Note:
As an alternative, we could make a "class BaseController : Controller" and make all our controllers inherit from "BaseController" (instead of Controller). Then we only have to set the attribute 1 global place (and don't need to register filter in Startup.cs).
Some people prefer the attribute style.
Example of usage:
[RequireHttpsAttribute]
public class BaseController : Controller
{
// Maybe you have other shared controller logic..
}
public class HomeController : BaseController
{
// Add endpoints (GET / POST) for Home controller
}

In Global.asax.cs, use "RegisterGlobalFilters" to register global attributes.
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new RequireHttpsAttribute());
//e.g. filters.Add(new HandleErrorAttribute());
//e.g. filters.Add(new System.Web.Mvc.AuthorizeAttribute());
}

You could use a base class for all of your controllers, and decorate that with the require ssl attribute.

Related

Can I get the required route in an Elmah filter?

In my ASP.NET MVC application, I have a very specific action I do not want Elmah to handle because it can emit 500 response status code as part of its normal functionning.
I don't want to be spammed by the errors that occur in this route, but I also don't want to shut down Elmah logging 500 errors everywhere else.
Is there any way to do this declaratively in the web.config, or do I have to write my own Assertion?
Or, to rephrase, is there any way to get the route with a binding in a test assertion?
PS: for those who wonder why on earth I would allow my action to generate a 500, it's because it's a wrapper around customer defined code, and I want my customers to know immediately if their custom code is buggy.
It looks like you are letting unhandled exceptions to be raised from that particular action. I guess so because if you just return a 500 error code without an exception, Elmah is not logging it:
public ActionResult ThrowError()
{
//your wrapper calling customer code
return new HttpStatusCodeResult(500, "Buggy code");
}
So I am assuming your action is throwing unhandled exceptions (possibly so your customers can see the exception details?). Let´s say your action method is in the HomeController and looks like this:
public ActionResult ThrowError()
{
//your wrapper calling customer code
// the customer code throws an exception:
throw new Exception("Buggy code");
}
Every time you go to /Home/ThrowError you will get an unhandled exception that results in a 500 and is logged by Elmah. So you need to filter this from Elmah, which gives you the following options
You could add the following filter for Elmah on your global.asax, telling Elmah to ignore exceptions on that particular url:
void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e)
{
var httpContext = HttpContext.Current;
if(httpContext == null) return;
if (httpContext.Request.Url.AbsolutePath == "/home/throwerror") e.Dismiss();
}
If you have enabled the ErrorFilter module, you can do the same in the web.config:
<system.web>
<httpModules>
<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
</httpModules>
</system.web>
<system.webServer>
<modules>
<add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
</modules>
</system.webServer>
<elmah>
<errorFilter>
<test>
<and>
<equal binding="Context.Request.ServerVariables['URL']" value="/home/throwerror" type="String" />
<!--You could also use a regex for the url-->
<!--<regex binding="Context.Request.ServerVariables['URL']"
pattern="/home/throwerror\d*" />-->
</and>
</test>
</errorFilter>
</elmah>
I don´t really like this approach which depends on listing all urls where you want to filter the exceptions (Or making sure all this urls follow a pattern you can express on a regex). If you can wrap exceptions thrown from the customer code into a specific exception, you could then filter all unhandled exceptions of that type no matter which url are they thrown from.
So you could create a new exception type and use it in your code wrapping customer´s code:
public class CustomerException : Exception
{
public CustomerException(Exception e)
: base(e.Message, e)
{
}
}
public ActionResult ThrowError()
{
try
{
//your wrapper calling customer code
// the customer code throws an exception:
throw new Exception("Buggy code");
}
catch (Exception e)
{
throw new CustomerException(e);
}
}
Now you can filter those exceptions from being logged in Elmah either in global.asax or web.config:
void ErrorLog_Filtering(object sender, ExceptionFilterEventArgs e)
{
var httpContext = HttpContext.Current;
if(httpContext == null) return;
if (e.Exception is CustomerException) e.Dismiss();
}
<errorFilter>
<test>
<jscript>
<expression>
<![CDATA[
// #assembly mscorlib
// #assembly WebApplication3
// #import WebApplication3.Controllers
// In my dummy project, CustomerException is located in WebApplication3.Controllers
$.Exception instanceof CustomerException
]]>
</expression>
</jscript>
</test>
</errorFilter>
Now with the last approach you don´t depend on the action urls.
Hope this helps!

How to return secure https links inc# MVC app

I recently purchased a SSL certificate from GoDaddy and was given a secured link to my website(https:mysite)
I can access all the links on my website securely.
The problem is unless someone knows the secure link they’ll continue to use http.
I wrote the site using C# MVC .
How can I enforce that when people go to specific page views that they are accessed securely using https.
Is there something that I need to add to my controller to ensure that the web protocol is https and not http when views are returned?
Do I need to do this at the webserver level or in code?
In addition to the RequireHttpsAttribute, you could also create a URL Rewrite rules within your web config
<rewrite>
<rules>
<rule name="Redirect HTTP to HTTPS" stopProcessing="true">
<match url="(.*)"/>
<conditions>
<add input="{HTTPS}" pattern="^OFF$"/>
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="SeeOther"/>
</rule>
</rules>
</rewrite>
You can do this using a global filter:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
// require https for all calls
filters.Add(new RequireHttpsAttribute());
}
}
And call it from your global.asax:
protected void Application_Start()
{
...
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
...
}

How to disable open ASP.NET MVC site in IFrame?

As I have already mentioned in topic, I have a MVC site and I need to disable loading it into IFrame.
I have created simple page for testing purpose and I try to load into two IFrames my site and Google.com. I can see that my site is loaded but Google isn't. It means that it's necessary to change something in my MVC site.
<!DOCTYPE html>
<html>
<body>
<iframe src="http://localhost:61831/" width="1200" height="800">
<p>Your browser does not support iframes.</p>
</iframe>
<iframe src="http://google.com" width="1200" height="800">
<p>Your browser does not support iframes.</p>
</iframe>
</body>
</html>
So what and where in MVC site I have to write to achieve that?
It is possible to use X-Frame-Options HTTP header attribute to avoid ASP.NET MVC application be opened in IFrame.
There are several different way to insert this attribute to HTTP header:
1.Configure IIS to add this attribute to all HTTP responses
2.Set this attribute in every necessary action method of every controller
public class HomeController : Controller
{
public ActionResult Index()
{
Response.AppendHeader("X-Frame-Options", "SAMEORIGIN");
return View();
}
}
3.Create C# attribute in a way described here and apply it to action methods and controllers
[HttpHeader("X-Frame-Options", "SAMEORIGIN")]
public class HomeController : Controller
{
public ActionResult Index()
{
}
}
4.Set this attribute in Global.asax file
public class MvcApplication : HttpApplication
{
protected void Application_Start()
{
...
}
protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
{
Response.AppendHeader("X-Frame-Options", "SAMEORIGIN");
}
}
Simple and quick Solution is to add following in Global.asax -
protected void Application_PreSendRequestHeaders(object sender, EventArgs e)
{
Response.AddHeader("X-Frame-Options", "SAMEORIGIN");
}
Then give a try with iframe. Pages will not open in iframes. HTH.
You can also add an entry in web.config:
<system.webServer>
...
<httpProtocol>
<customHeaders>
<add name="X-Frame-Options" value="SAMEORIGIN" />
</customHeaders>
</httpProtocol>
...
</system.webServer>
You can configure IIS to always append the X-Frame-Options SAMEORIGIN header in it's responses.
From IIS Management Console, select your application.
In the main panel, double click Http Response Headers.
Click Add from the upper right pane.
Set the name to X-Frame-Options and the value to SAMEORIGIN then
click OK.
This should prevent your site from being loaded into an iframe on a different host, without using any javascript or any extra code.
See developper.mozilla.org for the header documentation and technet.microsoft.com for IIS' configuration.
As DrewMan suggested, you want to use X-Frame-Options header.
I would suggest you to download Nuget Package NWebsec and there's MVC specific package. Also check out the configuration part.

How to change headers for specific types of files served in IIS / MVC

I am implementing a fix for the problem caused by 02 compression issues over 3G.
Web site exhibits JavaScript error on iPad / iPhone under 3G but not under WiFi
The best solution seems to be http://stuartroebuck.blogspot.com/2010/08/official-way-to-bypassing-data.html which is basically to add the header Cache-Control: no-transform in IIS, however I would like to apply this only to specific file types. What is the easiest way to do this?
It is the best solution for me to write a HttpModule. I wrote an example for you.
You can check mime type for specific file types.
public class AddHeaderModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.EndRequest += OnEndRequest;
}
void OnEndRequest(object sender, System.EventArgs e)
{
if(HttpContext.Current.Response.ContentType == "image/jpeg")
HttpContext.Current.Response.Headers.AddHeader("Cache-Control", "no-transform");
}
}
Also you have to add it web.config
<configuration>
<system.web>
<httpModules>
<add name="AddHeaderModule" type="your.namespace.AddHeaderModule" />
</httpModules>
</system.web>
</configuration>

How to skip static http requests when using one UoW per http request

I am using one NHibernate UoW per http request in my .net mvc 2 web application. I was just wondering how I can skip creating a UoW for static http requests like images.
You can convert your unit of work implementation to be an implementation of IActionFilter instead of an HttpModule. So OnActionExecuting you can begin your unit of work and OnActionExecuted you can end the unit of work. Then just apply it to your controllers and actions that do data processing.
you can use the IRequiresSessionState marker interface.
private void BeginTransaction(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
if (app.Context.Handler is IRequiresSessionState) {
// do work
}
}
the staticfilehandler does not apply Session to the request.
I'm not sure if the StaticFileHandler actually issues Begin/EndRequest but I'm guessing not. If you notice that it is triggered for your static files, I assume that your order of handlers are wrong or wildcards for handlers are wrong.
If you map "*" to you MVC handler, maybe you might need to actually move your static content into a subfolder and override the web.config and clear all handlers and only add StaticFileHandler on that.
I noticed that you mention that you use a IHttpModule for the UoW, so I'm guessing that you hijack that for every request.
You could do this:
create a subfolder that you call for example "/static/" and move your static files here. In that folder, create a web.config which contains the following:
<handlers>
<clear />
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" resourceType="Either" requireAccess="Script" />
</handlers>
and remove the UoW module here (maybe you would only need this row)
<modules>
<remove name="YourUoWModuleName" />
</modules>

Resources