CakePHP 4 $this->request->referer() not working in production

Written by James McDonald

June 24, 2020

Just had a situation where I couldn’t get a redirect to work. My CakePHP app is in an Apache enabled docker container behind an nginx reverse proxy

$this->redirect($this->request->referer());

The above simply refused to work in my development environment it worked fine

http://localhost:8090/blah/ worked fine

But

http://example.com/blah/

and it would return nothing

It turned out that if your hostnames don’t match i.e. your front end is accessed via https://example.com/blah and your backend is accessed via http://localhost:8090/blah then a call to $request->referer() return bupkis

This works although I imagine some sanitizing needs to be done to stop it just accepting any forged referer URL

$this->redirect($this->request->referer($local = false));

Safer


$referer = $this->request->referer(false);
if( preg_match('example.com', $referer ) === 1 ) {
     return $this->redirect($referer);
} else {
     return $this->redirect([ 'action' => 'index' ]);
}

Taking into account a trusted proxy (i.e. it can tell you what the external http_host name is)

        $this->request->trustProxy = true;
        $referer = $this->request->referer($local = false);
        $host = $this->request->host(); 
        $scheme = $this->request->scheme();

        if (preg_match('/^' . $scheme . ':\/\/' . $host . '/', $referer) === 1) {
            return $this->redirect($referer);
        }
        return $this->redirect(['action' => 'index']);

Nginx Configuration

Note I’m only trusting this because nginx is setting the headers:

upstream my-test {
    server 127.0.0.1:8999;
}
location /test {
    proxy_pass http://my-test;
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $server_name;
    proxy_set_header X-Forwarded-Proto https;
}

Notes about this nginx config

Front end running https Backend running http

This nginx frontend server block is running ssl enabled

The backend is running only http so specify http:// to upstream

proxy_pass http://my-test;

Proxy Pass Trailing Slash or Lack of it Changes the Behavior

Without a trailing slash

If you specify no trailing forward slash then the location and all subsequent pieces are passed. For example a request made to the front end nginx to:

https://example.com/test/controller/action
# is passed to the backend as
http://127.0.0.1:8999/test/controller/action

With a trailing slash

if you specify proxy_pass http://my-test/; a request to https://example.com/test/controller/action will be passed on without the location /test parameter

https://example.com/test/controller/action
# is passed to the backend as
http://localhost:8999/controller/action

X-Forwarded-Proto Header Required

I found if I didn’t set this header

proxy_set_header X-Forwarded-Proto https;

Then after I posted a delete e.g. POST /test/controller/delete/23 the 302 redirect issued to the client would have the wrong scheme in the location header:

location: http://example.com/test/controller/action

And then nginx would issue an internal redirect and mess-up the redirect

with the X-Forwarded-Proto header set it performed as required

location: https://example.com/test/controller/action

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

You May Also Like…

How to Research a CPU Upgrade

How to Research a CPU Upgrade

Upgrade Time! Doing a lot of VMWare Workstation virtualization to create labs for self-study and training. Finding...