Locally, I have my .env
configuration file property APP_URL
assocaiated to a custom DNS record of https://some-project.local
I then do the following to create a temporary signed URL:
$temporaryUrl = URL::temporarySignedRoute('temp.download', now()->addHours(24), ($struct = [
'uid' => Str::uuid()->toString(),
// ...
]));
Later, in my temp.download
route I do the following:
public function tempDownload(string $uuid, Request $request)
{
if ($request->hasValidSignature()) {
abort(401);
}
}
Locally, this is working fine. When I deploy this into development/testing/production environments inside Azure, it always hits 401.
I have updated my configuration to contain the APP_URL
and have asserted this via a SSH session running artisan tinker
:
Psy Shell v0.11.8 (PHP 8.0.27 — cli) by Justin Hileman
>>> env('APP_URL')
=> "https://XXXXXXXXX.azurewebsites.net"
It appears that the SSL termination inside Azure from the load balancer is screwing the signature. For this, I updated the TrustProxies
as suggested by multiple SO questions and the docs to the following:
class TrustProxies extends Middleware
{
protected $proxies = '*';
protected $headers = Request::HEADER_X_FORWARDED_ALL;
}
This, however, is still causing the 401 to hit. Any help appreciated on how I can get signed URL's to work inside Azure.
My NGINX configuration for the PHP-FPM pass from Nginx looks like this:
location ~ [^/]\.php(/|$) {
# Custom Azure configuration to support laravel
fastcgi_split_path_info ^(.+?\.php)(|/.*)$;
fastcgi_pass 127.0.0.1:9000;
include fastcgi_params;
fastcgi_param HTTP_PROXY "";
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param QUERY_STRING $query_string;
fastcgi_intercept_errors on;
# Azure pre-defined
fastcgi_connect_timeout 300;
fastcgi_send_timeout 3600;
fastcgi_read_timeout 3600;
# Buffers
fastcgi_busy_buffers_size 512k;
fastcgi_buffer_size 512k;
fastcgi_buffers 16 512k;
fastcgi_temp_file_write_size 512k;
# Server hardening
fastcgi_hide_header "x-powered-by";
}
Do I need to set anything in the X-FORWARDED-FOR
header to achieve this in Azure?
It is important to note that I use
server _;
as I have multiple servers (dev/test/prod) so I do not target one specific domain in my NGINX config.
Update - Doing some debug in Azure, I added the following route:
# Also tried 'X-Forwarded-Host'
Route::any('/test', fn(\Illuminate\Http\Request $request) => [$request->header('X-FORWARDED-HOST')]);
Which returns [null]
, this leads me to think I need some sort of Nginx configuration to make this work?
Also, if I tail -f /var/log/nginx/access.log
I can see the IP being logged is always the link-local address of the proxy (169.254.XXX.X). I think all of this just needs tweeking inside Nginx so FPM sees the correct values - how can I do this dynamically based on the server _
value?
Ok the issue I had was that locally, this didn't technically work.
In my NGINX configuration, I force a XDEBUG_SESSION_START
header in the requests to enable local debug from the mobile application etc.
This lead to me using the WRONG syntax locally, I was missing the !
operator:
if (!$request->hasValidSignature()) {
abort(401);
}
This lead to Azure working but not my local so I instead wrote this function to ignore additional params for local dev:
private function hasValidSignature(): bool
{
$url = rtrim(request()->url() . '?' . Arr::query(Arr::except(request()->query(), ['signature', 'XDEBUG_SESSION_START'])), '?');
$signature = hash_hmac('sha256', $url, app()->make('config')->get('app.key'));
return hash_equals($signature, (string) request()->query('signature')) && !(now()->getTimestamp() > (string) request()->query('expires'));
}