@ssnepenthe's aswer is right in saying that the hook you are using is not the right one something in incoming request.
Request information are available immediately to PHP, so you could use the earliest hook available to check them. And if you want to do this in the context of request API you should use the earliest hook of a REST API request. 'rest_pre_dispatch'
suggested by @ssnepenthe is fine, maybe another option could be rest_authentication_errors
that would allow you to return an error in case something is wrong.
But Jack Johansson is right in saying that HTTP headers (like the referer header used in @ssnepenthe's aswer) are not trustable, as they are very easily changed by the client. So it would be just like put a security guard in front of a door who just ask "it is safe to let you enter?" to anyone who want to go in: that's not going to work.
But the soluition proposed Jack Johansson's answer (a nonce) is not a real solution either: the whole point of nonces is to change with time, and a public API endpoint can't have things that change based on time. Moreover, WP nonces are trustable only when there's a logged-in user, which might not be the case for a public API and if an user is logged in, there's probably no reason to check the incoming domain: you trust the user, not the user machine.
So, what to do?
Well, even if HTTP headers are not trustable, not all the information available on $_SERVER
comes from headers.
Normally, all the $_SERVER
values whose keys starts that starts with HTTP_
comes from headers and have to be treated as unsafe user input.
But, for example, $_SERVER['REMOTE_ADDR']
contains the IP address used for the TCP connection to your server, which means it is trustable1.
Which also means, that either:
- properly configuring the server to generate the
$_SERVER['REMOTE_HOST']
value (for example in Apache you'll need HostnameLookups On
inside your httpd.conf
) that value
- using
gethostbyaddr
to do a reverse DNS lookup to resolve the domain name of the IP stored in $_SERVER['REMOTE_ADDR']
you could obtain quite reliably an host name that you could use to check against a whitelist (for the code, you could adapt the code from @ssnepenthe's aswer where you would replace $referer = $request->get_header('referer')
with $referer = gethostbyaddr($_SERVER['REMOTE_ADDR'])
).
But there's an issue.
If your webserver is behind a reverse proxy (quite common solution, actually) the TCP connection to the webserver is actually made by the proxy, so $_SERVER['REMOTE_ADDR']
will be the IP of the proxy, and not the IP of the client who originally sent the request.
The original request IP in such cases is usually available as $_SERVER['HTTP_X_FORWARDED_FOR']
, but being one of those $_SERVER
values that start with HTTP_
it is not really trustable.
So, if your webserver is behind a reverse proxy2 even the $_SERVER['REMOTE_ADDR']
would not be useful for such guard and a domain-based whitelist could only implemented at the proxy level.
In short, a reliable solution for API endpoint securing should be either implemented using some real authentication mechanism (e.g. oAuth) or should be done acting directly on the server configuration and not at application level.
Notes
1 Well, in theory it could be broken if someone hacked your ISP or if an attacker acts from inside your LAN, in both cases there's very little that you could do to be safe.
2 If you don't know if you are behind a reverse proxy, you can send a request from your local PC and check if $_SERVER['REMOTE_ADDR']
on the server match the local PC IP and also if $_SERVER['HTTP_X_FORWARDED_FOR']
is present and it matches the local PC IP.
$request
and$url
vars look like viavar_dump
or similar, I find that inspecting the inputs and outputs always lead to a proper answer. – farinspace Oct 5 '17 at 23:48