IIS reverse proxy configuration issues with MVC 5 app - asp.net-mvc

I cannot properly configure reverse proxy in IIS 8.5 for my MVC5 application. We use 3rd party application Tibco Spotfire WebPlayer 7.0.1 to integrate into our MVC webapp. To do so we use Javascript API to open report from WebPlayer and insert it into iframe.
We have to configure reverse proxy to allow requests go to backend server with WebPlayer installed. Our main webapp located at http://mywebapp.com/ and all requests that should be rewrite looks like http://mywebapp.com/SpotfireWeb/..., backend server located at private ip http://172.29.1.7/SpotfireWeb/...
The main issue that I configured reverse proxy using ARR 3.0 and URL Rewrite Module 2.0, but it doesn't work for *.ashx, *.aspx, *.axd, *.asmx files. It looks like some handler conflicts with ARR/RewriteModule and doesn't allow to pass requests to different server
Details:
When I try to load static html file like http://mywebapp.com/SpotfireWeb/secret.html the request is been rewrote to 172.29.1.7 server and I got content of this file. But once I request something like http://mywebapp.com/SpotfireWeb/GetJavaScriptApi.ashx?Version=6.0 I got 404 error from mywebapp.com. I run Wireshark and discovered that requests to back-end server 172.29.1.7 don't go at all if requests contains *.ashx file.
I installed Failed Requests Trace and found that event HANDLER_CHANGED occurred which changed ApplicationRequestRoutingHandler to System.Web.Mvc.MvcHandler
Playing with this I removed some handlers and now Wireshark catches requests to the back-end server. I believe this approach of removing handlers is not correct. May be I configured something in wrong way?
Anyway, removing one module by one I faced issue when POST request to http://mywebapp.com/SpotfireWeb/AjaxService.asmx/InitiateOpen didn't processed and I got 404 error from my initial app. When I removed *.asmx handler I still got the same error. This is point where I get stuck right now.
My rewrite rules from web.config of mywebapp.com. I configured it at website level:
<rewrite>
<rules>
<rule name="ReverseProxyInboundRule1" enabled="true" stopProcessing="true">
<match url="SpotfireWeb/?(.*)$" />
<action type="Rewrite" url="http://172.29.1.7/SpotfireWeb/{R:1}" logRewrittenUrl="true" />
<serverVariables>
<set name="ORIGINAL_HOST" value="{HTTP_HOST}" />
</serverVariables>
</rule>
</rules>
<outboundRules>
<clear />
<rule name="ReverseProxyOutboundRule1" preCondition="" enabled="true">
<match filterByTags="A, Area, Base, Form, IFrame, Img, Input, Link, Script" pattern="SpotfireWeb/?(.*)" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true" />
<action type="Rewrite" value="http://mywebapp.com/SpotfireWeb/{R:1}" />
</rule>
<rule name="ReverseProxy_Redirection" preCondition="IsRedirection" enabled="true">
<match filterByTags="A, Area, Base, Form, IFrame, Img, Input, Link, Script" serverVariable="RESPONSE_Location" pattern="^http://[^/]+/(.*)" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true">
<add input="{ORIGINAL_HOST}" pattern=".+" />
<add input="{URL}" pattern="^/(SpotfireWeb)/?.*" />
</conditions>
<action type="Rewrite" value="http://{ORIGINAL_HOST}/{C:1}/{R:1}" />
</rule>
<preConditions>
<preCondition name="IsRedirection">
<add input="{RESPONSE_STATUS}" pattern="3\d\d" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
Have anyone saw something similar ?
I assume that root cause in MVC handler, but can't understand where.

I fixed this. Obviously we don't need to remove modules. The answer basically into logs of failed requests.
During MAP_REQUEST_HANDLER event ASP.NET infrastructure determines the request handler for the current request. In my case MVC handler selected every time I requested URL like /SpotfireWeb/... regardless of ARR was current handler. Here I found that:
..the handler mapping can be changed during request execution in the MAP_REQUEST_HANDLER event. This allows scenarios such as URL rewriting to work.
My fix was removing SpotfireWeb from MVC routes completely. Thus I added following code into RouteConfig.cs
routes.IgnoreRoute("SpotfireWeb/{*pathInfo}");
And the whole class looks like:
public class RouteConfig
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
// ignore everything under 'SpotfireWeb' because this request will be routed
// to separate server via reverse proxy ARR/Rewrite module
routes.IgnoreRoute("SpotfireWeb/{*pathInfo}");
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { area = string.Empty, controller = "Home", action = "Index", id = UrlParameter.Optional },
namespaces: new[] { "GKSPortal.Controllers" });
}
}

Related

IIS URL Rewrite is not working when redirecting to another server

I need to redirect links to a different server using URL rewrite in IIS. These are SRSS report links. I have tried so many different configurations i have given up.
Old Link:
http://OldServer/ReportServer/Pages/ReportViewer.aspx?(reportnamehere - need to copy this part across to new link)
http://NewServer/ReportServer/Pages/ReportViewer.aspx?reportnamehere
Any ideas how this can be achieved?
This is a very simple rule requirement. Something like this will work. Basically just redirects to a new server appending the entire URL
<rule name="Redirect to newServer" stopProcessing="true">
<match url="(.*)" />
<action type="Redirect" url="http://NewServer/{R:1}" redirectType="Temporary" />
</rule>

Incorrect redirect from ASP.NET MVC application under URL Rewrite

I have the next configuration of websites in the IIS:
http://main.domain.com website (ASP.NET MVC 5 app, website binded to the specified host, port 80)
default website binded to :80 with ARR/URL Rewrite module enabled
A wildcard binding *.domain.com is specified in DNS settings.
The desired behavior is to have http://main.domain.com as an entry point and a set of dynamic user-subdomains like http://user1.domain.com, http://user2.domain.com, etc.
Now this behavior is simulated using links like http://main.domain.com/user/user1
I have set up the URL Rewrite rule for main.domain.com in the way like this:
<rule name="user-redirection" enabled="true" stopProcessing="true">
<match url="^.*$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true">
<add input="{HTTP_HOST}" pattern="^([\w\d-]+)\.domain\.com$" />
</conditions>
<action type="Rewrite" url="http://main.domain.com/user/{C:1}/{R:0}" logRewrittenUrl="true" />
</rule>
Everything is ok here - I can see that http://user1.domain.com works just like http://main.domain.com/user/user1 as it worked earlier.
Then I try to re-implement a logic of checking the existance of specified user in the database. For instance, when user47 doesn't exist - opening of the http://main.domain.com/user/user47 link leads to redirection to the http://main.domain.com entry point.
In the code-side it is done by adding custom filter attribute to the controller action that implements the needed conditional redirect. I have the next code:
public class UserController : Controller {
[CustomRedirectBehavior]
public ActionResult Index()
{
...
}
}
public class CustomRedirectBehaviorAttribute : ActionFilterAttribute {
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
...
if (redirectingCondition) {
filterContext.Result = new RedirectResult("http://main.domain.com");
}
}
}
And here.... I get a cyclic redirection error in the browser! To be certain - I have double-checked this behavior:
When I open http://main.domain.com/user/user47 I'm properly redirected to http://main.domain.com
When I open http://user47.domain.com/ I get a cyclic redirection error.
Then in order to investigate the problem I've modified redirect callback to:
filterContext.Result = new RedirectResult("http://someotherdomain.com/some/other/path");
And.... I can see that:
When I open http://main.domain.com/user/user47 I'm properly redirected to http://someotherdomain.com/some/other/path
When I open http://user47.domain.com/ I'm redirected to http://user47.domain.com/some/other/path !!! (And yes, that's not a typo and I've also double-checked this behavior)
So. I need an idea on how to pass through this problem
I've tried to reproduce your case and it seems you are really getting a redirecting loop. So I added a negative lookahead group ((?!main)) into the pattern and it works for me. This is how my web.config looks:
<rule name="user-redirection" enabled="true" stopProcessing="true">
<match url="^.*$" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="true">
<add input="{HTTP_HOST}" pattern="^(?!main)([\w\d-]+)\.domain\.com$" />
</conditions>
<action type="Rewrite" url="http://main.domain.com/user/{C:1}" logRewrittenUrl="true" />
</rule>
In order to investigate your problem I've tried:
Disable browser cache. If your browser using cache it might you will not see every change of the rule you made.
Change the type of action to be "Redirect" instead of "Rewrite" and watching what's going on in the "Network" tab in the "Developer tools (F12)".
e.g. <action type="Redirect" ...
Well... Finally I got the answer.
The problem was coming from the Application Request Routing (ARR) module being installed in order to make rewriting work with custom subdomains.
The Reverse rewrite host in response headers option was set to true by default after enabling proxy in the Server Proxy Settings area. Disabling this setting makes redirect working correctly under subdomain-based rewriting.

MVC routing for both .mvc and extensionless URLs

I have an odd question. I'm working on some MVC code that was setup to run extensionless, as in /Home/Index?id=10 However, it used to be setup for IIS 6, and was using the .mvc extension on all the controllers, as in /Home.mvc/Index?id=10.
I would like both routes to be able to work. In my global.asax.cs file, I created the following code:
public static void RegisterRoutes(RouteCollection routes)
{
routes.MapRoute("Default2",
"{controller}.mvc/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
routes.MapRoute("Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
}
This almost worked! But unfortunately when my controllers return RedirectToAction, the URL which is generated fails.
I call RedirectToAction like this:
return RedirectToAction("Index", "Account");
and it returns this:
/Account.mvc
Now Index is assumed so I'm not worried that that's missing. However, there is no / on the end of that URL, and because of that, it is returning a 404 error. If I add the / it works fine. Is there some way I can help MVC to generate the URLs correctly, or is there a way I can modify the routes to make it recognize this URL?
Background: I'm in this situation because I began converting my application to use extensionless URLs after we upgraded our webserver to IIS 7.5. I didn't realize that there were alot of links into the applications which had been shared with the outside world--changing to extensionless broke all those links. Doh! Should've left well enough alone.
I would personally use the URL Rewriter. You can Redirect ASP.NET Legacy URLs to Extensionless with the IIS Rewrite Module.
So for example you could create an outbound rules that looked something like (not exactly, they definitely need testing):
<outboundRules>
<rule name="remove .mvc outbound 1">
<match serverVariable="RESPONSE_LOCATION"
pattern="^/(.*).mvc$" />
<action type="Rewrite" value="/{R:1}" />
</rule>
<rule name="remove .mvc outbound 2">
<match filterByTags="A, Form, IFrame, Img, Input, Link, Script"
pattern="^/(.*).mvc$" />
<action type="Rewrite" value="/{R:1}" />
</rule>
</outboundRules>
If someone decided to hardcode an MVC extension...
<rule name="Add Slash Inbound">
<match url="(.*)" />
<conditions>
<add input="{PATH_INFO}" pattern="^/(.*).mvc$" negate="true" />
</conditions>
<action type="Rewrite" url="{R:0}/" />
</rule>
Looks like here is coded your solution with adding / at end of each requests Add a trailing slash at the end of each url?. If it is not enough We can try something else :)
For anyone trying to figure this out: the problem may not be that RedirectToAction is generating a bad URL, but actually that the URL is not being interpreted properly by IIS.
I discovered that other servers had no problem with the missing slash in the URLs described in the original question.
This led me to look at the App Pool setting. One setting in particular seems to affect this: "Enabled 32-bit applications". Setting this to true on the Application Pool enabled my application to parse URLs which were missing the trailing '/'.
Weird huh?

MVC RequireHttps Attribute is worthless?

I have a requirement to allow users to change their passwords via a form in my asp.net MVC application. My first thought was to decorate the ChangePassword action with a RequireHttps attribute.
However, I still have to send the password unencrypted before the attribute kicks in and returns "The requested resource can only be accessed via SSL". This defeats the purpose, doesn't it?
I am sure I am just confused and RequireHttps is useful; I would like to know if there is a way to use RequireHttps to achieve my aim. Alternatively, I would like to know any other way to achieve it.
UPDATE:
I now have some options thanks to the answers below - I can load the password inputs in an iframe using https, meaning that any posts from it will be encrypted. Other wise I can set the protocol to https in the code that constructs the post url:
var url = '#Url.Action("changePassword", "Security", new { area = "" }, "https")'
I'm not sure which is better, but I'm going to try the second one - any comments welcome.
Your application cannot control whether SSL is enabled. This depends only on web server configuration. The only thing you can do is make sure your application does not trust data that was not encrypted on the wire. RequireHttps does just that. Actions decorated with this attribute will never processes data that was sent in plain text.
The real use case of the RequireHttpsAttribute is to enforce the https:// scheme only when authentication is requested. Not in all cases. The RequireHttpsAttribute only implements the OnAuthentication method of the IAuthenticationFilter interface.
As the OnAuthentication method is only called from within the InvokeAuthenticationFilters method, I wouldn't use the RequireHttpsAttribute attribute.
To properly enforce https:// on certain controllers or actions, I'd create my own attribute, based on ActionFilterAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class EnforceHttpsActionFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
if (new[] { "GET", "HEAD" }.Any(verb => String.Equals(filterContext.HttpContext.Request.HttpMethod, verb, StringComparison.OrdinalIgnoreCase))) ;
{
string url = "https://" + filterContext.HttpContext.Request.Url.Host + filterContext.HttpContext.Request.RawUrl;
filterContext.Result = new RedirectResult(url);
}
}
}
To enforce https:// for the whole site, you can get inspired by a web.config markup that I've used for *.azurewebsites.net instances of our sample app.
<system.webServer>
<rewrite>
<rules>
<rule name="HTTPS Redirect in Azure">
<match url="(.+)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
<add input="{HTTP_HOST}" pattern="^(.+)\.azurewebsites.net(.*)$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="SeeOther" />
</rule>
</rules>
</rewrite>
</system.webServer>
Note: The [RequireHttps] attribute does not handle HEAD requests - and instead gives the exception, so certain spiders or pre-fetching tools will get an error if they attempt to hit your site.
It is best anyway to do something like this in IIS with the rewrite module.
<rule name="Redirect to http" enabled="true" patternSyntax="Wildcard" stopProcessing="true">
<match url="*" negate="false" />
<conditions logicalGrouping="MatchAny">
<add input="{HTTPS}" pattern="off" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" redirectType="Found" />
</rule>
Taken from here: https://blogs.technet.microsoft.com/dawiese/2016/06/07/redirect-from-http-to-https-using-the-iis-url-rewrite-module/
Important Tip: Don't forget to reinstall the rewrite module when migrating to a new server - the error you get if you forget is somewhat obtuse!

IIS 7.5 URL Rewrite - Redirect from http to https for account controller but from https to http for everything else

I've found bits and pieces of what I need to make this work, but haven't been able to bring everything together into a workable solution.
I am working on an intranet site, and want to secure *just the logon and logoff actions on my account controller with https. I have the certificate installed correctly, and can successfully redirect traffic to these controller actions to https using a UrlRewrite rule:
<rule name="Redirect to HTTPS" stopProcessing="true">
<match url="^account/logon$|^account/logoff$" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/{R:0}" redirectType="Permanent" />
</rule>
Now, however, I also want to redirect *all of the rest of my site's requests (other than traffic to the two actions) back to http. I'm not interested in debating the merits of this approach, as I have what I consider valid reasons for wanting to redirect back out of https to http.
I've tried writing some code in the Actions to achieve this, but am having major issues with this. I don't know if it is because I'm working with two load-balanced servers or what, but anything I try just gives me a "too many redirects" error message.
So, two questions:
Is it better to use a UrlRewrite rule to redirect out of https or a controller actions?
Does anyone have a working code example or something that can at least get me started down the right path?
Any help is much appreciated!
Better late than never. This might help you or someone else.
<rule name="Redirect to HTTP">
<match url="secureDir/(.*)" negate="true" />
<conditions>
<add input="{HTTPS}" pattern="^ON$" />
</conditions>
<action type="Redirect" url="http://{HTTP_HOST}{REQUEST_URI}" />
</rule>
<rule name="Redirect to HTTPS" stopProcessing="true">
<match url="secureDir/(.*)" />
<conditions>
<add input="{HTTPS}" pattern="^OFF$" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}{REQUEST_URI}" />
</rule>
First, you will have a bit of a challenge here insofar as non-page resources -- how are you referencing images and stylesheets?
As for the question at hand, I think you want to force this upstream of the web servers, such as on the load balancer. I would also backstop the account controller by adding a RequireHttps attribute.
Finally, remember that even if the login process is secured, if that cookie is not transmitted over HTTPS you can easily end up with a firesheep like scenario.
I have some code here which lets you control this with attributes.
MVC already has a [RequireHttps] attribute which you would apply to your logon/logoff pages. My code extends this approach and gives you an additional [ExitHttpsIfNotRequired] attribute. With this attribute applied to your base controller, when you try to access any action with HTTPS that doesn't have [RequireHttps], it will redirect you to HTTP.

Resources