jurian sluiman

{
Wissel naar

In control of HTTPS for action controllers

I work more and more with web applications where users persist personal data and realize the functions to access this data requires a secured connection. Currently I do not use SSL for login, but for next projects I would like to incorporate this into the Soflomo system. [1]

To enable an SSL config with Apache2 on a Linux system (in our case Ubuntu Linux) the Ubuntu wiki provides a very good tutorial how to complete this in a couple of minutes with self-signed certificates. The certificates can be bought by certified organizations of course, but in my development environment, self-singed certificates are good enough. [2]

Next, I want to create the control for http/https environments in the Zend Framework as easy as possible. Therefore I create an action helper for the Zend Framework's action controllers to force pages to be served in https and, when necessary, in http. There are a few assumptions/requirements for this helper:

  1. Determine http/https requirements on action basis
  2. By default, no redirect happens (the action accepts both http and https access)
  3. When necessary, http and https can be forced for specific actions

To fulfill this list, an action helper is created which is small in lines of code, but does exactly what it must do.

class Soflomo_Controller_Action_Helper_Https extends Zend_Controller_Action_Helper_Abstract
{
    protected $_https = array();
    protected $_http = array();
    
    public function forceHttps (array $actions)
    {
        $this->_https = $this->_filter($actions);
        return $this;
    }
 
    public function forceHttp (array $actions)
    {
        $this->_http = $this->_filter($actions);
        return $this;
    }

    public function preDispatch ()
    {
        $action = $this->getRequest()->getActionName();
        $scheme = $this->getRequest()->getScheme();
        $url    = $this->getFrontController()->getParam('domainName')
                . $this->getRequest()->getRequestUri();

        if (in_array($action, $this->_https) && $scheme === 'http') {
            $this->getActionController()
                 ->getHelper('redirector')
                 ->gotoUrlAndExit('https://' . $uri);
        } elseif (in_array($action, $this->_http) && $scheme === 'https') {
            $this->getActionController()
                ->getHelper('redirector')
                 ->gotoUrlAndExit('http://' . $url);
        }
    }

    protected function _filter (array $actions)
    {
        $methods = get_class_methods($this->getActionController());
$actionKey = ucfirst($this->getRequest()->getActionKey());
        
        foreach ($actions as $key => $action) {
$action = $action . $actionKey;
            if (!in_array($action, $methods)) {
                unset($actions[$key]);
            }
        }

        return $actions;
    }
}

The action helper is initiated by using either the function forceHttps() or forceHttp(). You can pass an array of actions to both functions. The actions are filtered, so it is sure the list consist of actions which are available in the action controller.

At predispatch, the current action and the list of http/https actions are compared. When the current action is in the list of enforced http/https actions and the current scheme does not match, the redirect happens. [3]

Example

This is a small action controller scaffold demonstrating the working principle of the action helper.

class Application_FooController extends Zend_Controller_Action
{
public function init ()
{
$this->https->forceHttps(array('bar'))
->forceHttp(array('baz'));
}

public function BarAction () {}

public function BazAction () {}

public function BatAction () {}

When you visit foo/bar, you get redirected to the https connection. Browsing to foo/baz, you are enforced to use http. And for foo/bat you do not get redirected anyway, because the system is not configured for this action. [4]

Notes

[1] Of course, login is the most common task, but for e.g. e-commerce we have input of address information at the shopping cart and confirmation of payment. In many more occasions the SSL might be handy.

[2] It is likely you want to run multiple applications from your localhost server and therefore you can run into problems with SSL and the Apache2 vhost structure. Use the NameBasedSSLVHostWithSNI directive to enable multiple SSL sites under one ip address (your localhost).

[3] The redirect url ($url) is composed of a domain name and a request uri. For every application I build, the domainName param is set in the application.ini and that is how I fetch the domain name in this case. For various other benefits this param is set, and you can set the param by adding this rule to your application.ini:

resources.frontController.params.domainName = "my.domain.tld"

If you do not want such parameter, you need to change the action helper. For example, you can use the request object to get the server name:

$domainName = $this->getRequest()->getServer('SERVER_NAME');

[4] I simplified the usage of action helpers by extending the Zend_Controller_Action class and add __get() and __call(). With this change you can access action helpers by $this->https or $this->https() in your action controller. I wrote more about this change in an earlier blog post.

Comments

Sven

I've used routing to do some https (login, ordering).
Will do the task I wanted ;)

Also handy when usingmultiple domain entries.

Jurian Sluiman

How do you use the routes to redirect to the correct scheme? That is -I think- the biggest issue when you deal with SSL enforced pages.

I don't know how to attach the (for example) login route to a https:// scheme, but thereafter you still need a safety net to catch requests that ask for the same URI but with the http:// scheme.

Sven

Hi,

I use route chaining to chain the route to an domain and scheme.

I use somthing like this (sorry for any typo's, i did not copy it):

[code]
// Hostname based routing.
$hostnameRoute = new Zend_Controller_Router_Route_Hostname(
'http://www.example.com',
array(),
array(),
'http'
);

// Hostname based routing (ssl).
$hostnameRouteSSL = new Zend_Controller_Router_Route_Hostname(
'http://www.example.com',
array(),
array(),
'https'
);

$route = new Zend_Controller_Router_Route(
'login/*',
array(
'module' => 'default',
'controller' => 'user',
'action' => 'login'
)
);

// Add route to router
$router->addRoute('login', $hostnameRouteSSL->chain($route));

[/code]

Then you can use the route name to create https://www.example.com/login

For example: $this->view->url(array(), 'login', true);

Maybe it isn't the best solution, but in my case it works well and does exactly what I needed ;-)

Place a comment

If you have a user account for this site, you can login to click here.

Please note your comment below will be removed if you login!

 
 

The address is stored internally but not displayed on this site. We will respect your privacy.

In your message no html is allowd. A blank line creates a new paragraph, an url gets a hyperlink.