Web developer and entrepreneur

SSL Redirect Filter for Symfony

Filed Under PHP, Security, Symfony - October 25th, 2006 5:05pm

Serving secure pages on a website is made a lot easier by having both your secure and insecure pages served by the same files - all you need to do is change the scheme from http:// to https:// and you’re done. But how do you manage which pages should redirect to the secure server? And how do you make sure you redirect users back when they’re finished submitting sensitive information?

The answer again is in Symfony’s filter framework, which can be used to automatically monitor whether your requests should change scheme, and redirect when needed.

Oh - one more thing. I don’t want redirection to take place in development mode.

Note: this technique won’t work if your secure and insecure URLs point to different directories. Sorry, but I haven’t taken that scenario into consideration because I haven’t had to deal with it yet :). If you’re really stuck email me and I’ll see what I can do.

First add the following settings to your app.yml file:

1
2
3
4
5
6
7
8
all:
  ssl:
    insecure_host:    blog.phpdeveloper.co.nz
    secure_host:      secure.phpdeveloper.co.nz
    secure_actions:
      - { module: shop, action: register }
      - { module: shop, action: checkout }
      - { module: register, action: updateCardDetails }

The insecure_host and secure_host keys should be the hostnames of your HTTP server (port 80) and HTTPS server (port 443) respectively. Under secure_actions put the list of module/action combinations which should always be secure. Best practice is to make both the form editing page and the form submission page secure, to give your users peace of mind throughout the process.

Next we add our filter code to lib/sslFilter.class.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
 * Filter for redirecting to SSL for the pages that need it
 *
 * @author James McGlinn <james@mcglinn.org>
 * @version 2
 */
class sslFilter extends sfFilter
{
  /**
   * Execute filter
   *
   * @param FilterChain $filterChain The symfony filter chain
   */
  public function execute ($filterChain)
  {
    // Only execute this filter once
    if ($this->isFirstCall() && SF_ENVIRONMENT != 'dev') {
      // Array of modules/actions that require move to SSL
      $ssl_actions = sfConfig::get('app_ssl_secure_actions');
 
      if (empty($_SERVER['HTTPS']) && count($_POST) < 1) {
 
        // We're not using SSL and not POSTing data - check if we should be using SSL
        foreach ($ssl_actions as $action) {
          if ($this->getContext()->getModuleName() == $action['module'] && $this->getContext()->getActionName() == $action['action']) {
            $new_url = sprintf('https://%s%s', sfConfig::get('app_ssl_secure_host'), $_SERVER['REQUEST_URI']);
            header('Location: ' . $new_url);
            exit;
          }
        }
 
        // Using secure host when not required - not good
        if ($_SERVER['HTTP_HOST'] == sfConfig::get('app_ssl_secure_host')) {
          $new_url = sprintf('http://%s%s', sfConfig::get('app_ssl_insecure_host'), $_SERVER['REQUEST_URI']);
          header('Location: ' . $new_url);
          exit;
        }
      } elseif (!empty($_SERVER['HTTPS']) && count($_POST) < 1) {
 
        // We're using SSL and not posting data
        $dont_redirect = false;
        foreach ($ssl_actions as $action) {
          if ($this->getContext()->getModuleName() == $action['module'] && $this->getContext()->getActionName() == $action['action']) {
            $dont_redirect = true;
          }
        }
        if ($dont_redirect == false) {
          // Redirect
          $new_url = sprintf('http://%s%s', sfConfig::get('app_ssl_insecure_host'), $_SERVER['REQUEST_URI']);
          header('Location: ' . $new_url);
          exit;
        }
      }
    }
    // Next filter
    $filterChain->execute();
  }
}

Finally, enable your sslFilter in the application’s config/filters.yml configuration file:

1
2
sslFilter:
  class:  sslFilter

Run symfony cc to clear your cache and the filter will be working. Your URLs can remain the same and any insecure requests for actions listed in app.yml will be redirected to the same URL on the secure server. The only exceptions are requests with POST data, which won’t be redirected as the data would be lost.

Conversely, any requests made through the secure server for pages not in the list will redirect the user back to the insecure version of the page. That reduces the work your server has to do and decreases load times for the user, keeping everyone happy.

Comments

7 Responses to “SSL Redirect Filter for Symfony”

  1. COil on November 17th, 2006 10:13 pm

    I have modified a little bit the code to secure a whole module or a couple module/action.

    In 1st bloc

    // We're not using SSL and not POSTing data - check  if we should be using SSL
    foreach ($ssl_actions as $action) {
     
      // Check a particular action in a module
      if ($action['action']) {
        if ($this->getContext()->getModuleName() == $action['module'] && $this->getContext()->getActionName() == $action['action']) {
          $redirect = true;
        }
      // Redirection on the whole module
      } elseif ($this->getContext()->getModuleName() == $action['module']) {
        $redirect = true;
      }
     
      if ($redirect == true) {
        $new_url = sprintf('https://%s%s', sfConfig::get('app_ssl_secure_host'), $_SERVER['REQUEST_URI']);
        header('Location: ' . $new_url);
        exit;
      }
    }

    In 2nd bloc :

    // Check a particular action in a module
    if ($action['action']) {
      if ($this->getContext()->getModuleName() == $action['module'] && $this->getContext()->getActionName() == $action['action']) {
        $dont_redirect = true;
      }
    // Redirection on the whole module
    } elseif ($this->getContext()->getModuleName() == $action['module']) {
      $dont_redirect = true;
    }

    Note that the following code leads to an infinite redirection if the secure and unsecure section have the same url excepting the “s”, i had to remove it for my server configuration.

    if ($_SERVER['HTTP_HOST'] == sfConfig::get('app_ssl_secure_host'))  {
      $new_url = sprintf('https://%s%s', sfConfig::get('app_ssl_insecure_host'), $_SERVER['REQUEST_URI']);
      header('Location: ' . $new_url);
      exit;
    }

    :)

  2. ruzz on March 2nd, 2007 10:37 pm

    great way to handle this. makes selective implementation a breeze. Far less invasive than other possible ways of handling this, and great that it redirects non secure pages back to non secure hosting.

    on the downside, filters seem to slow down the overall performance–I guess what we gain in the abstraction we give up in the localization. less code, less intrusion, but a check on every page load for the site, every time.

    well done.

  3. Robbie Cluton on December 2nd, 2008 7:03 am

    Two years later, still helping =)

  4. Implementing SSL with Symfony « Knowledge is Power on April 16th, 2009 12:15 am
  5. Symfony 1.2 redirect specific modules and actions to HTTPS (SSL) « SayNoToFlash on August 11th, 2009 2:20 pm

    [...] scripts that I found online, and created a very simple filter to handle this. This was inspired by this script, and the unacceptably poor example in the Symfony 1.2 [...]

  6. dan on September 5th, 2009 1:27 am

    Hi,

    I’m getting this in the Apache log :
    PHP Fatal error: Class ’sslFilter’ not found in ***\modules_home_config_filters.yml.php on line 6

    What could be the problem ?

    Thank you

  7. James McGlinn on September 7th, 2009 6:17 pm

    Hi Dan, sounds like you need to run “./symfony cc” to clear your cache. After that the filter class file should be picked up by the autoloader.

Leave a Reply