Recently I worked on a project that happens to be architected this way. (Not my decision)
React.js Frontend -> GraphQL Server -> Cloudflare -> Laravel Backend REST APIs
Naturally getting the real IP of the visitor is very troublesome. Previously when it was just Cloudflare sitting in front of the Laravel app. Getting the visitor IP is a simple case of configuring https://github.com/fideloper/TrustedProxy and you can continue to use your usual request()->ip()
However, in this new architecture, you have a GraphQL server in front of Cloudflare, making things not as straight forward.
But still, the solution is simple after some dive into Laravel's source code
- Make your GraphQL server forward the visitor IP to Cloudflare on the
X_FORWARDED_FOR
header. - In your Laravel App, when you fetch the request IP, instead of doing
request()->ip()
, dolast(request()->getClientIps())
- Done!
Now for the explanation:
- First, read the Cloudflare docs on
X-Forwarded-For
- In short, the real visitor IP is the last one on
X-Forwarded-For
header - Now let's look at the code behind Laravel's
request()->getClientIps()
/**
* Returns the client IP addresses.
*
* In the returned array the most trusted IP address is first, and the
* least trusted one last. The "real" client IP address is the last one,
* but this is also the least trusted one. Trusted proxies are stripped.
*
* Use this method carefully; you should use getClientIp() instead.
*
* @return array The client IP addresses
*
* @see getClientIp()
*/
public function getClientIps()
{
$ip = $this->server->get('REMOTE_ADDR');
if (!$this->isFromTrustedProxy()) {
return array($ip);
}
return $this->getTrustedValues(self::HEADER_X_FORWARDED_FOR, $ip) ?: array($ip);
}
As we can see we need to grab the last item in the array returned by getClientIps()
. In PHP there is the end(&$array)
method but doing so will give you PHP Fatal error: Only variables can be passed by reference
error so you would need an extra line to assign the response of getClientIps()
to a variable first.
$clientIps = request()->getClientIps();
$visitorIp = end($clientIps);
But thankfully, Laravel has a last($array)
helper method which avoids the need for that and calls end(&$array)
behind the scenes.
/**
* Get the last element from an array.
*
* @param array $array
* @return mixed
*/
function last($array)
{
return end($array);
}
I hope this article helps anyone who is facing a simular issue. Good night :)