Yii 1.1: URL management for Websites with secure and nonsecure pages

33 followers

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.

Total 10 comments

#17421 report it
maxtorchel at 2014/06/09 11:57am
hostInfo

can i set hostInfo and secureHostInfo to relative url (without domain name)? I wont to set one site to different domains.

#16288 report it
tpandi at 2014/02/06 08:42am
SSL needs

Hi,

I am new in yii . I don't know how to use the UrlManager class. Kindly explain brifly

#15362 report it
prazi at 2013/11/01 02:44am
Spot On

Thanks vey much. This was exactly what I wanted. Worked like a charm. Cheers.

#14477 report it
HermesCK at 2013/08/15 06:37am
Thanks

Many thanks Qiang ... perfect and simple solution

#14265 report it
nguyendh at 2013/07/30 07:28pm
awesome trick

Thanks so much Qiang

Saving me tons of trouble working with Apache ReWrite.

#13690 report it
M0ka at 2013/06/17 11:55am
Nice article

Thanks a lot

#13582 report it
Hasan Tokatlı at 2013/06/08 03:30am
How to force all routes to https?

Thanks for the article. This is an essential wiki.

My customer wanted me to serve all pages via https protocol. So using this wiki I added all of the sites controllers in

'secureRoutes'

array. (site route too) Is this solution effective for making all routes secure? Thanks.

#10659 report it
Gerhard Liebenberg at 2012/11/12 09:56am
secureRoutes => insecureRoutes

Dear Qiang, thank you for saving us lots of time.

A business related site would probably have many secure pages and just a few insecure pages. So I would like to have things turned around and have an array of insecureRoutes. Only the few pages in this array will be http. All the rest (not in the array) will be https. Many thanx.

#10348 report it
Antonio Ramirez at 2012/10/22 04:21am
Nice one

Thanks Qiang... very good article.

#10341 report it
bonnie at 2012/10/21 08:19am
Nice article

Thanks for this article. This is what I was wondering how to do it in my project am working on.

Leave a comment

Please to leave your comment.

Write new article
  • Written by: qiang
  • Category: How-tos
  • Yii Version: 1.1
  • Votes: +24
  • Viewed: 17,960 times
  • Created on: Oct 19, 2012
  • Last updated: Oct 20, 2012
  • Tags: URL