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!
Related
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" });
}
}
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.
I have created a website on Azure, and linked it with custom domain name(CNET).
Now, when I look at domains on website configuration on Azure panel I see both www.mywebsite.com , and default mywebsite.azurewebsites.net. Both of these domains work fine and I can access website using any of these.
How can I remove mywebsite.azurewebsites.net domain? Does having both of these domains affect SEO?
EDIT*
Thanks for answers, I am trying to enable a 301 redirect, but it is not working. I have added this to web.config file ("example" being my actual site name)
<system.webServer>
<rewrite>
<rules>
<rule name="SEOAzureRewrite" stopProcessing="true">
<match url=".*" />
<conditions>
<add input="{HTTP_HOST}" pattern="^example.azurewebsites.net$" />
</conditions>
<action type="Redirect" url="http://www.example.com/{R:0}" redirectType="Permanent" />
</rule>
</rules>
</rewrite>
</system.webServer>
I run the website but nothing happens. I can still access the mysite.azurewebsites.net address.
there is no problem in having both domains pointing to your website.
for SEO, you must do 301 redirect from mywebsite.azurewebsites.net to www.mywebsite.com.
It has to be 301 redirect. As this will tell the search engines to always index www.mywebsite.com
Since I couldn't set up the url rewrite in web.config, I created global filter to check for azure url and display error if so. Here is the filter, and I added a new view in my error pages for this purpose that just says "Page doesn't exist" to avoid indexing by search engines. Think this will solve possible duplicate indexing issues.
public override void OnResultExecuting(ResultExecutingContext context)
{
if (context.RequestContext.HttpContext.Request.Url != null)
{
string path = context.RequestContext.HttpContext.Request.Url.AbsoluteUri;
if (path.ToLower().Contains("mywebsite.azurewebsites") && !path.ToLower().Contains("error/oldazuresubdomainredirect650"))
{
throw new HttpException(650, "Azure legacy");
}
}
}
You can create a url rewrite rule to do exactly that
You just need to add a redirect rule to your site’s web.config file. You can do that by adding the following rewrite rule to the web.config file in your wwwroot folder. If you don’t have a web.config file, then you can create one and just paste the text below into it, just change the host names to match your site’s host names:
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Redirect rquests to default azure websites domain" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAny">
<add input="{HTTP_HOST}" pattern="^yoursite\.azurewebsites\.net$" />
</conditions>
<action type="Redirect" url="http://www.yoursite.com/{R:0}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
The above is all you need to do to get it to work. If you'd like to better understand what exactly that code does and why it works, there's a detailed writeup at https://zainrizvi.io/blog/block-default-azure-websites-domain/
We are building a new mobile based website for an already existing website (that is used heavily by the client).
In this scenario when a user requests certain webpages on the existing web-application from a mobile device, the request for the existing web application must be Redirected to the new mobile web application.
To summarize, we have the following conditions-
If the web page request contains a Query String (jobId), it must be
redirected to a mobile web page (JobDtls.aspx) used with another
Query String parameter name (jId); but with the same query string
value.
If the web page request does not contain a query string, the
Redirection must be to the default.aspx page of the mobile web site.
Both the above conditions must work only if the request is through a
mobile device.
For this Task, I came up with 2 different rewrite rules as described below. However, since I am new to this topic, I wanted to know if someone could optimize on this configuration; with maybe 1 Rule instead.
<rewrite>
<rules>
<rule name="Mobile Entry With QueryString" stopProcessing="true">
<match url="Job.aspx" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{HTTP_USER_AGENT}" pattern="midp|mobile|phone" />
<add input="{QUERY_STRING}" pattern="jobid=([0-9]+)$" />
</conditions>
<action type="Redirect" url="htps://{HTTP_HOST}/MWeb/mjobitem.aspx?jid={C:1}" rdirectType="Permanent" appendQueryString="false" />
</rule>
<rule name="Mobile Entry Without QueryString" stopProcessing="true">
<match url="Job.aspx" />
<conditions logicalGrouping="MatchAll" trackAllCaptures="false">
<add input="{HTTP_USER_AGENT}" pattern="midp|mobile|phone" />
<add input="{QUERY_STRING}" pattern=".+" negate="true" />
</conditions>
<action type="Redirect" url="https://{HTTP_HOST}/MWeb" redirectType="Permanent" appendQueryString="false" />
</rule>
</rules>
</rewrite>
First, it does not looks like your case may benefit of any form of combination of your rules.
"Combining" redirect rules makes especially sense when you want to avoid redirects chaining. Your case is not leading to redirects chaining. See Combine Multiple IIS7 Rewrite Rules (Redirects) Into One for more on this (and especialy answer link, if the answer is still not completed with the solution provided by the link). Moreover, it is not really combining rules in one, it does allow them to run and accumulates prior to performing the final resulting action.
Then, it looks like we can not have in a same rule some conditions with logicalGrouping MatchAll and others with logicalGrouping MatchAny, which would be required for combining conditions of both rules. An other way would be to elaborate a more complex regular expression matching both query string condition. But it would lead to something harder to understand, so this looks to me undesirable.
And this is just for combining conditions. You would have another trouble to tackle: actions are not the same. I do not think there is a way there to express an action handling both of your cases.
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.