Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ZF2 and force HTTPS for specific routes

I'm trying to achieve forced https when accessing any page under /account route. I've found this question ZF2 toRoute with https and it works... partially. My routes:

'router' => array(
    'routes' => array(
        'account' => array(
            'type' => 'Scheme',
            'options' => array(
                'route' => '/account',
                'scheme' => 'https',
                'defaults' => array(
                    'controller' => 'Account\Controller\Account',
                    'action' => 'index',
                ),
            ),
            'may_terminate' => true,
            'child_routes' => array(
                'default' => array(
                    'type' => 'Literal',
                    'options' => array(
                        'route' => '/',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'index',
                        ),
                    ),
                ),
                'signin' => array(
                    'type' => 'Segment',
                    'options' => array(
                        'route' => '/signin[/:type]',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'signin',
                        ),
                        'constraints' => array(
                            'type' => '[a-zA-Z][a-zA-Z0-9-_]*',
                        ),
                    ),
                ),
                'signout' => array(
                    'type' => 'Segment',
                    'options' => array(
                        'route' => '/signout',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'signout',
                        ),
                    ),
                ),
                'register' => array(
                    'type' => 'Segment',
                    'options' => array(
                        'route' => '/register[/:step]',
                        'defaults' => array(
                            'controller' => 'Account\Controller\Account',
                            'action' => 'register',
                        ),
                        'constraints' => array(
                            'step' => '[a-zA-Z][a-zA-Z0-9-_]*',
                        ),
                    ),
                ),
            ),
        ),
    ),
),

and a home route from Application module from Skeleton Application (cloned from github). Whenever I access any subroute of /account it throws 404:

http(s)://domain.my/account/signin = 404, wrong
http(s)://domain.my/account/* = 404, wron
https://domain.my/signin = signin page, wrong should be /account/signin
http://domain.my/ = ok, main page
http://domain.my/account = 404, wrong
https://domain.my/ = wrong, account page should be main page

Generally my problem is: the page should be accessed by http or https BUT /account and it subroutes have to be accessed only by https.

EDIT

Ok, I've tried the chained_routes but this is not what I wanted to achieve. I want to do something like this:

User not logged in: types: http://domain.my/account -> redirected to https://domain.my/account/login (I know I can achieve this with $authService->hasIdentity()) then redirect to https://domain.my/account

types: http://domain.my/account/login -> redireted to https://domain.my/account/login

types: http://domain.my/account/edit -> redirected to https://domain.my/account/login then to https://domain.my/account/edit

same with logged user when he access anything from /account route it is redirected to the same url but with https.

like image 262
Dawid Avatar asked May 07 '26 04:05

Dawid


2 Answers

If you want to redirect a user, you can't do this with routes. Simply said, you have to accept the route match first, then check if the scheme used is https and if not, redirect. This will be controller logic then. So ignore the scheme route in your use case and check https in your controller.

In Zend Framework 1, we had a custom helper Https which you could use to force a page to be redirected to https if the scheme was http:

public function init ()
{
    $this->https->forceHttps(array('index', 'login', 'edit'));
}

public function indexAction ()
{
    // code here
}

public function loginAction ()
{
    // code here
}

public function editAction ()
{
    // code here
}

If you hit index, login or edit on http, you would be redirected to https. If you used https, there was no redirect.

Currently we do not have such plugin for Zend Framework 2, but I think that's the solution you have to look for. Make the feature a controller plugin, so you can reuse it among different controllers. An example for Zend Framework 2 might more like this:

use Zend\Http\Response;

public function loginAction()
{
    // If return value is response, this means the user will be redirected
    $result = $this->forceHttps();
    if ($result instanceof Response) {
        return $result;
    }

    // code here
}

The controller plugin might look like this:

use Zend\Uri\Http as HttpUri;

class ForceHttps extends AbstractPlugin
{
    public function __invoke()
    {
        $request = $this->getController()->getRequest();

        if ('https' === $request->getUri()->getScheme()) {
            return;
        }

        // Not secure, create full url
        $plugin = $this->getController()->url();
        $url    = $plugin->fromRoute(null, array(), array(
            'force_canonical' => true,
        ), true);

        $url    = new HttpUri($url);
        $url->setScheme('https');

        return $this->getController()->redirect()->toUrl($url);
    }
}

Note I have not tested this, so there might be a few bugs in the code. But you should get the idea by this example.

like image 186
Jurian Sluiman Avatar answered May 09 '26 04:05

Jurian Sluiman


I've had a similar problem but approached it using events. Redirecting is a cross cutting concern, if you include in each controller it gets hard to maintain. This code shows how you can redirect all http requests to https. If you only want some you can add logic into the doHttpsRedirect(). An array in the config file showing which actions should redirect would be a simple way of adding this logic.

class Module
{
    ...

    public function onBootstrap(MvcEvent $e){
        $em = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($em);
        $em->attach('route', array($this, 'doHttpsRedirect'));
        ...
    }

    public function doHttpsRedirect(MvcEvent $e){
        $sm = $e->getApplication()->getServiceManager();
        $uri = $e->getRequest()->getUri();
        $scheme = $uri->getScheme();
        if ($scheme != 'https'){
            $uri->setScheme('https');
            $response=$e->getResponse();
            $response->getHeaders()->addHeaderLine('Location', $uri);
            $response->setStatusCode(302);
            $response->sendHeaders();
            return $response;
        }
    }

    ... 
}
like image 27
codebrent Avatar answered May 09 '26 02:05

codebrent



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!