Hi Eric,
Yes that looks pretty much like where I ended up yesterday. This morning I went back a step and thought
Why aren't I doing this on NGINX?
The down side of handling this in Laravel is that you have to boot your application every time you access a video. The response will take longer compared to the response direct from the NGINX web server which would serve the video normally for you if it was under the public folder. We would have to make this trade off of security vs performance.
Instead, we can use the secure link module of NGINX. To get this you either need to compile the source or apt get install nginx-extras
The Secure Link module verifies the validity of a requested resource by comparing an encoded string in the URL of the HTTP request with the string it computes for that request. If a link has a limited lifetime and the time has expired, the link is considered outdated. The status of these checks is captured in the $secure_link variable and used to control the flow of processing.
There are two secure_link methods available. The preferred method in this case is enabled by the secure_link and secure_link_md5 directives. Here the encoded string is an MD5 hash of variables defined in the NGINX configuration file. Most commonly, the $remote_addr variable is included to restrict access to a particular client IP address, but you can use other values, for example $http_user_agent, which captures the User-Agent header and so restricts access to certain browsers.
Optionally, you can specify an expiration date after which the URL no longer works even if the hash is correct.
The client must append the md5 argument to the request URL to specify the hash. If an expiration date is included in the string that is hashed, the client also must append the expires argument to specify the date.
Within the NGINX default configuration file, we have included a section
location /secure/ {
secure_link $arg_md5,$arg_expires;
secure_link_md5 "$secure_link_expires$uri$remote_addr MySupirS3cretPass";
if ($secure_link = "") { return 403; }
if ($secure_link = "0") { return 410; }
}
In this instance our server listens to incoming requests and handles all secured HTTP(/S) requests under the location /secure/ block.
The secure_link directive defines two variables that capture arguments in the request URL: $arg_md5 is set to the value of the md5 argument, and $arg_expires to the value of the expires argument.
The secure_link_md5 directive defines the expression that is hashed to generate the MD5 value for the request; during URL processing, the hash is compared to the value of $arg_md5. The sample expression here includes the expiration time passed in the request (captured in the $secure_link_expires variable), the URL ($uri), the client IP address ($remote_addr), and the secret word "MySupirS3cretPass"
If the hash in the URL sent by the client (captured in the $arg_md5 variable) does not match the hash calculated from the secure_link_md5 directive, NGINX sets the $secure_link variable to the empty string. The if test fails and NGINX returns the 403 Forbidden status code in the HTTP response.
If the hashes match but the link has expired, NGINX Plus sets the $secure_link variable to 0; again the if test fails but this time NGINX returns the 410 Gone status code in the HTTP response.
Generating the hash and expiration time is easy. Rather than writing a laravel package or class, I just created a helper.
private function buildSecureLink($baseUrl, $path, $secret, $userIp)
{
$expires = Carbon::now()-addHour()->toTimestamp();
$md5 = md5("$expires$path$userIp $secret", true);
$md5 = base64_encode($md5);
$md5 = strtr($md5, '+/', '-_');
$md5 = str_replace('=', '', $md5);
return $baseUrl . $path . '?md5=' . $md5 . '&expires=' . $expires;
}
// example usage
$secret = 'MySupirS3cretPass';
$baseUrl = 'https://www.videos.com';
$path = '/secure/2018/thru-2018-02-14/20180214-013000.mp4';
$userIp = $_SERVER['REMOTE_ADDR'];
$secureLink = buildSecureLink($baseUrl, $path, $secret, $userIp);
Alternatively you may want to create a time from now in seconds.
private function buildSecureLink($baseUrl, $path, $secret, $ttl, $userIp)
{
$expires = time() + $ttl;
$md5 = md5("$expires$path$userIp $secret", true);
$md5 = base64_encode($md5);
$md5 = strtr($md5, '+/', '-_');
$md5 = str_replace('=', '', $md5);
return $baseUrl . $path . '?md5=' . $md5 . '&expires=' . $expires;
}
// example usage
$secret = 'MySupirS3cretPass';
$baseUrl = 'https://www.videos.com';
$path = '/secure/2018/thru-2018-02-14/20180214-013000.mp4';
$ttl = 240; //no of seconds this link is active
$userIp = $_SERVER['REMOTE_ADDR'];
$secureLink = buildSecureLink($baseUrl, $path, $secret, $ttl $userIp);
This approach makes a lot more sense to me as I can use a proxy_pass in the same block if I need to fetch the files from a back-end webserver and I dont have to boot Laravel to serve the video files.
Greg