tags:

views:

222

answers:

3

I've written a web application using Catalyst that has a lot of forms and needs to run over https. There are no hard-coded URLs, everything uses $c->uri_for or $c->req->uri. Everything worked great in the development environment using the dev server running over http.

Today, when I went ahead and deployed the application, I noticed a problem. The way our production environment is currently setup, client browsers talk to a F5 load-balancer over HTTPS and the F5 talks to the web server on the internal network over HTTP.

[ Browser ] ---HTTPS---> [ F5 ] ---HTTP---> [ Web Server ]

Now, because the web server only gets HTTP requests, all URIs are generated starting with HTTP. This means:

<form action='[% c.uri_for('/secure/form') %]' method='post'>

becomes:

<form action='http://websitename.org/secure/form' method='post'>

and now all browsers complain you are submitting data over an insecure connection. I need that c.uri_for to begin with https.

The app needed to go live today, so I did a mass search/replace for all form actions to this:

<form action='[% c.uri_for('/secure/form') | replace('http:', 'https:'%]' method='post'>

Well, now that breaks development, so I conditionalized the form actions based on a config key:

[% IF c.config.production %]
  <form action='[% c.uri_for('/secure/form') | replace ('http:', 'https:') %]' method='post'>
[% ELSE %]
  <form action='[% c.uri_for('/secure/form') %]' method='post'>
[% END %]

Needless to say, this all just seems wrong on multiple levels. Anyone have a better idea? And is there a way to force $c->uri_for to generate a URI that begins with https?

Solution

If you're using Catalyst 5.80008 or later, set MyApp->config(using_frontend_proxy => 1); and simply have your proxy set the X-Forwarded-Port header. For Catalyst versions prior to 5.80008, still set using_frontend_proxy so you get the actual client_ip, but to generate the correct URIs have your web server set the environment variable HTTPS to ON

A: 

I don't use Catalyst, but the docs for uri_for point to the request object's base method.

I read the source, and found that base is a read/write method. So, you should be able to squeeze $req->base('https://foo.bar.com/') into your code somewhere to get your https uris.

Update:

singingfish says that the above advice is incorrect--which wouldn't surprise me at all, it was based on a quick look at TFM. S/He also says that the scheme method should be set instead. I assume s/he is referring the the uri object's scheme method.

Further searching turned up HTTPS Tricks on the Catalyst Wiki. It shows an example of setting the scheme on the uri objects returned by the uri_for_action method. It looks like you would need to set the scheme on every uri you request, all over your code. So, I can't help but feel that the scheme method may not be the best choice.

I also found this thread on the mailing list. Setting base or setting an environment variable are both recommended methods.

This gives you several avenues of investigation. Good luck.

daotoad
This is wrong. The correct accessor to modify is the `->scheme` method.
singingfish
+4  A: 

The following works (tested):

In MyApp.pm, add the following sub:

sub secure_uri_for {
    my ($self, @args) = @_;
    my $u = $self->uri_for(@args);
    $u->scheme('https');
    return $u;
}

Now any time you want a guaranteed https, you can call $c->secure_uri_for('whatever')

singingfish
this may not actually answer your question though. Do you have `using_front_end_proxy 1` in your application's config?
singingfish
+5  A: 

You might try this configuration option:

MyApp->config(using_frontend_proxy => 1);

It's described in Catalyst's documentation

zakovyrya
ah, yes, documentation ... I guess I should have read all of it first. Well, I just dumped the headers and while the F5 is setting 'X-forwarded-for' it is not setting 'X-Forwarded-Host' or 'X-Forwarded-Port'. I'll have those configured, set the config option and let everyone know how it does. thanks!
mhchaudhry
Ok, tested. You're correct in that I needed to set `using_frontend_proxy` in my config. However, checking the `X-Forwarded-Port` header was only added in Catalyst 5.8.0008 and I'm using 5.71. However, you did point me in the correct direction, and after looking at the code for `Catalyst::Engine::CGI` I figured all I had to do was add `SetEnv HTTPS on` in my virtual host configuration. Everything works now!
mhchaudhry