URL management for Websites with secure and nonsecure pages

In this article, I will describe how to manage URLs for a Website that has both secure and nonsecure content.

Secure content are sent via the https protocol using SSL (secure socket layer), while nonsecure content via the normal http protocol. For simplicity of description, let's call the former https content/page while the latter http content/page. A serious Website often needs to serve some pages in https and some in http. For example, to prevent password sniffing, we would like to serve the login page using https; and to save server processing power, we would like to serve insensitive content (e.g. home page) using http.

A requirement is that when we are in an https page we want to generate URLs to http pages, and vice versa. For example, a Web site has a main menu shared by all pages, and the main menu contains links to both http page (e.g. About page) and https page (e.g. Login page). If we are now in an http page, we can use relative URLs (e.g. /about) to link to other http pages, but we have to use absolute URLs with https protocol to link to the https pages. And if we are in an https page, the situation will be the other way around.

Another requirement is that if a secure page is requested via an http request, we should redirect the browser to use https protocol; and vice versa. The redirection usually should be 301 permanent redirection. It is possible to achieve this goal via the rewrite rules of Web servers. But the rewrite rules could become very complex if we want fine-grained control of secure and nonsecure content.

To achieve the above two requirements, we can extend [CUrlManager] as follows,

class UrlManager extends CUrlManager
{
	/**
	 * @var string the host info used in non-SSL mode
	 */
	public $hostInfo = 'http://localhost';
	/**
	 * @var string the host info used in SSL mode
	 */
	public $secureHostInfo = 'https://localhost';
	/**
	 * @var array list of routes that should work only in SSL mode.
	 * Each array element can be either a URL route (e.g. 'site/create') 
	 * or a controller ID (e.g. 'settings'). The latter means all actions
	 * of that controller should be secured.
	 */
	public $secureRoutes = array();
	
	public function createUrl($route, $params = array(), $ampersand = '&')
	{
		$url = parent::createUrl($route, $params, $ampersand);
		
		// If already an absolute URL, return it directly
		if (strpos($url, 'http') === 0) {
			return $url;  
		}
		
		// Check if the current protocol matches the expected protocol of the route
		// If not, prefix the generated URL with the correct host info.
		$secureRoute = $this->isSecureRoute($route);
		if (Yii::app()->request->isSecureConnection) {
			return $secureRoute ? $url : $this->hostInfo . $url;
		} else {
			return $secureRoute ? $this->secureHostInfo . $url : $url;
		}
	}

	public function parseUrl($request)
	{
		$route = parent::parseUrl($request);
		
		// Perform a 301 redirection if the current protocol 
		// does not match the expected protocol
		$secureRoute = $this->isSecureRoute($route);
		$sslRequest = $request->isSecureConnection;
		if ($secureRoute !== $sslRequest) {
			$hostInfo = $secureRoute ? $this->secureHostInfo : $this->hostInfo;
			if ((strpos($hostInfo, 'https') === 0) xor $sslRequest) {
				$request->redirect($hostInfo . $request->url, true, 301);
			}
		}
		return $route;
	}

	private $_secureMap;

	/**
	 * @param string the URL route to be checked
	 * @return boolean if the give route should be serviced in SSL mode
	 */
	protected function isSecureRoute($route)
	{
		if ($this->_secureMap === null) {
			foreach ($this->secureRoutes as $r) {
				$this->_secureMap[strtolower($r)] = true;
			}
		}
		$route = strtolower($route);
		if (isset($this->_secureMap[$route])) {
			return true;
		} else {
			return ($pos = strpos($route, '/')) !== false 
				&& isset($this->_secureMap[substr($route, 0, $pos)]);
		}
	}
}

Now in the application configuration, we specify UrlManager as our URL manager instead of the default [CUrlManager]:

return array(
    // ....
    'components' => array(
        'urlManager' => array(
            'class' => 'UrlManager',
            'urlFormat' => 'path',
            'hostInfo' => 'http://example.com',
            'secureHostInfo' => 'https://example.com',
            'secureRoutes' => array(
            	'site/login',   // site/login action
            	'site/signup',  // site/signup action
            	'settings',     // all actions of SettingsController
            ),
        ),
    ),
);

In the above code, we configure UrlManager to secure the login, sign-up and settings pages. If you want to secure other pages, just add the corresponding routes to the secureRoutes array shown above.

We can now use Yii::app()->createUrl() as usual in places where we need to create URLs. Our UrlManager will automatically determine if the generated URLs need to be prefixed with proper host info. The UrlManager will also do the 301 redirection work if needed.

19 0
34 followers
Viewed: 59 203 times
Version: 1.1
Category: How-tos
Tags: URL
Written by: qiang
Last updated by: qiang
Created on: Oct 20, 2012
Last updated: 12 years ago
Update Article

Revisions

View all history

Related Articles