appharbor force HTTPS for static files - appharbor

I have a requirement to ensure all traffic on my website is redirected to HTTPS if it is requested over HTTP. When we deploy the site to appharbor, we use the custom RequireHttpsAttribute which works well for our MVC controllers.
However we also want to force any request for static files (images, stylesheets, javascript) via HTTP to be sent to HTTPS. Trying it using the web.config rewrite rules ends up in a redirect loop due to the load balancer sending https request to the web server as http.
Does anyone have any ideas on how to achieve this?

After receiving a reply from appharbor support, one of their suggestions was to implement code similar to the custom RequireHttpsAttribute for static files as well.
So I created a class called HttpRequestModule, and set it up to run for all requests (runAllManagedModulesForAllRequests set to true) I was able to force any direct requests to HTTP urls to redirect to HTTPS.
class HttpRequestModule : IHttpModule
{
public void Init(HttpApplication app)
{
app.BeginRequest += new EventHandler(CheckHttpRequest);
}
private void CheckHttpRequest(object sender, EventArgs a)
{
if (app.Context.Request.IsSecureConnection) return;
if (app.Contact.Request.IsLocal) return;
if (string.Equals(app.Context.Request.Headers["X-Forwarded-Proto"],
"https",
StringComparison.InvariantCultureIgnoreCase))
{
return;
}
var secureUrl = "https://" + app.Context.Request["HTTP_HOST"] + HttpContext.Current.Request.RawUrl;
app.Context.Response.Redirect(secureUrl);
}
}

Related

ratpack: implementing a custom PublicAddress to force https

I was on a pac4j mail thread discussing why the redirect url ratpack-pac4j uses is using http even when the original page request is served over https. I did some digging and it looks like ratpack-pac4j is using the InferringPublicAddress. This works for local development, but because my elasticbean host proxies 443 to 80, ratpack-pac4j thinks it's over http and uses http for the redirect url. I want this call to be over https when on the server.
I'm trying to register a custom PublicAddress class that always returns http in development mode, but https in production mode:
.registry(Guice.registry(b -> {
b
.module(SessionModule.class)
.module(ThymeleafModule.class, conf -> conf.templatesMode("LEGACYHTML5"))
.add(PublicAddress.class, ForcedHttpsPublicAddress.create()); //PublicAddress.class, PublicAddress.inferred("https"));
}))
...
static private class ForcedHttpsPublicAddress implements PublicAddress
{
static ForcedHttpsPublicAddress create() {
return new ForcedHttpsPublicAddress();
}
ForcedHttpsPublicAddress() {
}
}
But when I call ctx.get(PublicAddress.class), it's still returning InferringPublicAddress. Am I registering the custom PublicAddress incorrectly?
Got help on the Ratpack forum. I needed to bind it instead of add it.
.bind(PublicAddress.class, ForcedHttpsPublicAddress.class)

Get "time of upload started"

I'm developing a WebAPI service in which you can upload a file. The Action looks something like this:
[HttpPost]
public async Task<IHttpActionResult> PostAsync(byte[] content)
{
var now = DateTime.UtcNow;
}
The client which are using the WebAPI also provides a timestamp as header which is used together with some HMAC-stuff to authenticate. One part of the auth check is to validate the timestamp. We parse the timestamp and checks if it is +/- 5 minutes from now. If not then the auth fails.
It works great for all our API calls except this upload API (in some cases). The problem is that sometimes a user uploads a large file over a slow connection and therefore it takes more than 5 minutes to upload the file and the point in time where we check is AFTER the whole file has been uploaded.
Therefore:
Can we somehow do the HMAC check BEFORE the whole file is uploaded? (the file itself (HTTP Content) is not used in the HMAC check). Today we are using an ActionFilter.
Can I get the "time of request" (first byte arrived or whatever) in my Action code?
Thanks!
So, after some investigation I came up with a much better solution:
Use a HTTP Module to do the actual HMAC authentication.
After reading this blog post (http://blogs.msdn.com/b/tmarq/archive/2007/08/30/iis-7-0-asp-net-pipelines-modules-handlers-and-preconditions.aspx) I got a much better understanding of how IIS Works.
I decided to use a HTTP Module which is invoked before the MVC Action.
The code ended up like this:
public class HmacModule : IHttpModule
{
public void Init(HttpApplication context)
{
EventHandlerTaskAsyncHelper taskAsyncHelper = new EventHandlerTaskAsyncHelper(Authenticate);
context.AddOnBeginRequestAsync(taskAsyncHelper.BeginEventHandler, taskAsyncHelper.EndEventHandler);
}
private async Task Authenticate(object sender, EventArgs e)
{
var context = ((HttpApplication)sender).Context;
var request = context.Request;
var authResponse = await CheckAuthentication(request);
if (!authResponse.HasAccess)
{
context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
context.Response.StatusDescription = authResponse.ErrorMessage;
if (authResponse.Details != null)
context.Response.Write(authResponse.Details);
context.Response.End();
}
}
}
I hope this helps others in the same situation...

Redirect permanently 301 in blogengine.net (global.asax)

i want to redirect my old address www.informarea.it/BlogEngine to new address www.informarea.it...
*my global.asax of blogengine.net is *
void Application_BeginRequest(object source, EventArgs e)
{
HttpApplication app = (HttpApplication)source;
HttpContext context = app.Context;
// Attempt to perform first request initialization
FirstRequestInitialization.Initialize(context);
}
*can i make to apply the code of redirect permanently? *
if (app.url.ToString().ToLower().Contains("http://www.informarea.it/BlogEngine"))
{
HttpContext.Current.Response.Status = "301 Moved Permanently";
HttpContext.Current.Response.AddHeader("Location",url.Replace("http://www.informarea.it/blogengine", "http://www.informarea.it"));
}
Can Someone help me?
thank you very much
Fabry
This should redirect any query where the path starts with /BlogEngine to the same url with the /BlogEngine removed.
if(Request.Url.PathAndQuery.StartsWith("/BlogEngine", StringComparison.OrdinalIgnoreCase)) {
Response.RedirectPermanent(Request.Url.PathAndQuery.Substring(11), true);
}
Pros:
Gives a 301 Redirect like you requested
Keeps the rest of the path and query string intact for the following request
Cons:
Requires .net 4.0 (Version 2.7 of BlogEngine is targeted at 4.0 so I don't think this will be an issue.)

Response code 401 triggering basic authentication before the jquery ajax error handler

I have a scenario where I have to handle authentication of ajax requests using "Forms Authentication". Based on some search and help from my earlier stackoverflow post, I had decided to use the method described at here.
The idea is to send back a 401 response for unauthenticated requests, and then handle that in the AJAX error handler. So I have an AJAX error handler in my ASP.net MVC3 Layout page that redirects the browser to the login page when it receives 401 response on unauthenticated ajax requests. Here is the ajax error handler.
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
if (jqXHR.status == "401") {
window.location.replace(loginUrl);
}
....
});
This all works well on my local IIS 7.5 Server. But on the server where my site is hosted, unfortunately, I get a basic authentication popup on unauthenticated ajax requests (for example session timed out), before the AJAX error handler runs and redirects the browser to the login page. When I cancel the "Authentication Required" popup by pressing the Cancel button, the AJAX error handler then runs and I am redirected to the login page.
So, why does the browser show the authentication popup before running the AJAX error handler?
Edit: The Hosting Server is running IIS 6.
as Softlion said
This is a common question with an easy answer. the 401 is transformed into a 302 to the login >page by the .net authorization module. The browser never see the 401 only the 302.
if you are using .net 4 and later, you use code below
HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
it work's fine for me.
This is a common question with an easy answer.
the 401 is transformed into a 302 to the login page by the .net authorization module. The browser never see the 401 only the 302.
Of course this is not playing nicely with ajax calls.
The best solution i tryed and i'm currently using involve writing a new attribute which is catching 401 and tranform it into ... 409 which is catched by the jquery ajax handler.
It is part of a paid product so i can not give any code.
Try to remove WWW-Authenticate header from response.
IIS 6 in integrated mode? I don't believe there is any such thing, unless you're talking about integrated authentication.
My guess is that you're using a non-aspx extension, so on IIS6 this means that it's not even hitting the .net process. So, IIS is using it's own 401 error response page.
Likely, the solution is to force all requests to be handled by the .net process.
Your host will have to go into IIS properties > configuration > wildcard mappings - and map everything to the .net process.
.net won't catch the 401 errors. What I did was to set the IIS error page from the default 401 page to my own static 401 page. From that page I used javascript to redirect to another handler.
The solution here is to write a custom HttpModule to workaround the MVC frameworks default behavior. Once I was finally able to register the module (cheers David Ebbo) it worked for me. You may want to choose your own criteria for calling SuppressAuthenticationRedirect.
public class SuppressFormsAuthenticationRedirectModule : IHttpModule {
private static readonly object SuppressAuthenticationKey = new Object();
public static void SuppressAuthenticationRedirect(HttpContext context) {
context.Items[SuppressAuthenticationKey] = true;
}
public static void SuppressAuthenticationRedirect(HttpContextBase context) {
context.Items[SuppressAuthenticationKey] = true;
}
public void Init(HttpApplication context) {
context.PostReleaseRequestState += OnPostReleaseRequestState;
context.EndRequest += OnEndRequest;
}
private void OnPostReleaseRequestState(object source, EventArgs args) {
var context = (HttpApplication)source;
var response = context.Response;
var request = context.Request;
if (response.StatusCode == 401 && request.Headers["X-Requested-With"] ==
"XMLHttpRequest") {
SuppressAuthenticationRedirect(context.Context);
}
}
private void OnEndRequest(object source, EventArgs args) {
var context = (HttpApplication)source;
var response = context.Response;
if (context.Context.Items.Contains(SuppressAuthenticationKey)) {
response.TrySkipIisCustomErrors = true;
response.ClearContent();
response.StatusCode = 401;
response.RedirectLocation = null;
}
}
public void Dispose() {
}
public static void Register() {
DynamicModuleUtility.RegisterModule(
typeof(SuppressFormsAuthenticationRedirectModule));
}
}
For me this ended up being simple. Most IIS web sites with anonymous authentication also have a default Windows Auth enabled
Turn off the Windows Authentication which is what pops up the login screen when the site detects the 401 even from an ajax call.
<security>
<authentication>
<anonymousAuthentication enabled="true" />
<windowsAuthentication enabled="false" />
</authentication>
</security>
Wait ! I thought you said Ajax request, how can you get a popup on ajax request ? I am pretty sure somewhere else you are triggering the call to the URL even before AJAX call. From your scenario its proved that when you cancel the popup, your actual ajax request is being made and hence you can do a ajax redirect.
The idea is to send back a 401 response for unauthenticated requests, and then handle that in the AJAX error handler
You can get an ajax response only if you send a ajax request, if you send normal http request then you will get a popup. This has nothing to do with .Net or Java :)

Handling URL's with AppHarbor without Modifying All My Controllers

I'm trying to host an MVC 3 application (FunnelWeb) on AppHarbor. For a reason that's still not clear to me, when my route is only a Controller+Action (e.g. mysite/admin is Admin+Index and mysite/login is Admin+login) everything works fine, but if I have anything else in the route (e.g. a variable like {*page}) my URL will be mysite:12345/mypage (where 12345 is a port number assigned by AppHarbor and mypage is the name of the page I'm requesting). This makes the request fail as the port 12345 is not publicly exposed.
AppHarbor uses load balancing to distribute the request between multiple IIS's. This is their way of doing stuff and this is why internally the requests are routed to some non-standard ports. I don't have a problem with that, but I have problem with MVC that tries to route me to that internal URL.
I'm not pointing fingers here; it's nobody's fault :) so let's move to the question:
Why there is a difference between requesting a route with Controller+Action only and requesting a route with a variable like {*page}? Be technical please :)
Here is an example of how to handle requests in AppHarbor, however, it seems that it requires me to modify all my controllers (OMG). Is there any way to implement this without modifying my controllers?
Any other suggestions are welcomed :)
Thanks in advance.
UPDATE: Coincidentally, the behaviour that I observed matches the conclusion that I reached. However, the issue has nothing to do with ASP.Net MVC routing. The short story is, FunnelWeb forces lowercase URL's, so, whenever it receives a request to a resource it convert it to lowercase, if needed, and issue a 301 response. The problem is, when creating the URL for the 301 response, the request URL (absolute URL) is now the URL used when the request made from the load balancer to IIS and not the one made from the client; hence, the request fails.
This is known issue with FunnelWeb url generation on AppHarbor. When using standard MVC methods to generate relative URLs, this is not a problem. AppHarbor has a short guide and sample on how the generate public URLs in the knowledge base.
It's possible that the following is now all you need:
<appSettings>
<!-- AppHarbor Setting to stop AppHb load balancer internal port numbers from showing up in URLs-->
<add key="aspnet:UseHostHeaderForRequestUrl" value="true" />
</appSettings>
This is noted as an update on AppHarbor's support page at http://support.appharbor.com/kb/getting-started/workaround-for-generating-absolute-urls-without-port-number
MSDN says the following about UseHostHeaderForRequestUrl:
aspnet:UseHostHeaderForRequestUrl - If this value attribute is false [default], the Url property is dynamically built from the host, port, and path provided by the web server. If this value attribute is true, the Url property is dynamically built by using the host and port provided by the incoming "Host" header and the path provided by the web server.
There is a way, but it requires a couple of classes.
When ASP.NET MVC registers a route, it defines a route handler. This route handler returns a HTTP handler that handles the request. If you use a custom route handler that returns a custom HTTP handler, you can rewrite the HTTP context by using a couple decorator classes.
Start by creating a HttpContextProxy and HttpRequestProxy that derives from the base classes and wraps all methods and properties to an inner instance. I've made the hard work available.
Next create the decorators, first the HTTP context decorator:
using System.Web;
public class HttpContextDecorator : HttpContextProxy
{
public HttpContextDecorator(HttpContextBase innerHttpContext)
: base(innerHttpContext)
{
}
public override HttpRequestBase Request
{
get
{
return new HttpRequestDecorator(base.Request);
}
}
}
The HTTP request decorator:
using System;
using System.Web;
public class HttpRequestDecorator : HttpRequestProxy
{
public HttpRequestDecorator(HttpRequestBase innerHttpRequest)
: base(innerHttpRequest)
{
}
public override bool IsSecureConnection
{
get
{
return string.Equals(Headers["X-Forwarded-Proto"], "https", StringComparison.OrdinalIgnoreCase);
}
}
public override Uri Url
{
get
{
var url = base.Url;
var urlBuilder = new UriBuilder(url);
if (IsSecureConnection)
{
urlBuilder.Port = 443;
urlBuilder.Scheme = "https";
}
else
{
urlBuilder.Port = 80;
}
return urlBuilder.Uri;
}
}
public override string UserHostAddress
{
get
{
const string forwardedForHeader = "HTTP_X_FORWARDED_FOR";
var forwardedFor = ServerVariables[forwardedForHeader];
if (forwardedFor != null)
{
return forwardedFor;
}
return base.UserHostAddress;
}
}
}
As mentioned, you also need to override the MVC classes - here the HTTP handler:
using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
public class CustomMvcHandler : MvcHandler
{
public CustomMvcHandler(RequestContext requestContext)
: base(requestContext)
{
requestContext.HttpContext = new HttpContextDecorator(requestContext.HttpContext);
}
protected override IAsyncResult BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, object state)
{
httpContext = new HttpContextDecorator(httpContext);
return base.BeginProcessRequest(httpContext, callback, state);
}
protected override void ProcessRequest(HttpContextBase httpContext)
{
httpContext = new HttpContextDecorator(httpContext);
base.ProcessRequest(httpContext);
}
}
Then the route handler:
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
public class CustomMvcRouteHandler : MvcRouteHandler
{
protected override IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new CustomMvcHandler(requestContext);
}
}
Finally, you'll need to replace the associated handler for all registered routes (or map them properly from the beginning):
var routes = RouteTable.Routes.OfType<Route>().Where(x => x.RouteHandler is MvcRouteHandler);
foreach (var route in routes)
{
route.RouteHandler = new CustomMvcRouteHandler();
}

Resources