1

I have a Server A which has a database of valid download tokens.

If the user provides a valid token (present in the database), a large file (> 2 GB) will be available as download for them. Currently, I do (as detailed in Fastest Way to Serve a File Using PHP and Downloading files with download.php):

<?php
$ok = check_token_in_database($_GET['token']);   // internally uses a SQLite DB
$file = "bigfile.zip";
if ($ok) {
    header("X-Sendfile: /path/to/" . $file);
    header("Content-type: application/octet-stream");
    header('Content-Disposition: attachment; filename="' . $file . '"');
}

It works, but now I need to lighten the load of Server A, and I would like, if the token is valid, to serve the file from another Server B. I was thinking about:

<?php
$ok = check_token_in_database($_GET['token']);
if ($ok) {
    header("Location: https://server_b.example.com/obfuscated_url_ff87a45d76apZ/bigfile.zip");

but then, anyone will probably be able to find the destination link https://server_b.example.com/obfuscated_url_ff87a45d76apZ/bigfile.zip and they can share it with other people, which I don't want.

How to handle this situation, without having to move the token database and the token check to Server B?

Basj
  • 41,386
  • 99
  • 383
  • 673
  • Just validate the token on server B, and you can check if the user in server B has the same IP address as the user in server A who generated the token, to prevent link sharing – HTMHell Jan 23 '21 at 11:59
  • Thanks @HTMHell, but as mentioned in my last sentence, I'd like to avoid doing the token checking on Server B, because this would require for me to move the DB (which is currently generated and stored on Server A), or sync it very often, etc. – Basj Jan 23 '21 at 12:01
  • You don't have to move the database, just connect to your existing database. When connecting instead of connecting to "localhost", pass server A's IP address – HTMHell Jan 23 '21 at 12:01
  • @HTMHell ServerA and ServerB are not on the same network; also currently the token database is an SQLite DB, so not easy to connect to it from another server (moreover, in another network!) – Basj Jan 23 '21 at 12:03
  • If you really don't want to validate the token in front of a database in server B, you can use something like JWT. Then you have the secret in both server A which generates the token and B who validates it. In the JWT token, you can also store the IP address – HTMHell Jan 23 '21 at 12:03
  • Thank you! I think you can post this an anwser @HTMHell. Can this be easily done from PHP on both Server A and Server B? – Basj Jan 23 '21 at 12:14
  • Yes, it can easily be done using a JWT library, I've posted an answer – HTMHell Jan 23 '21 at 12:45

1 Answers1

1

You can use JWT for generating the tokens. In the payload, you can store the user's IP address to prevent link sharing.

I'm not sure about which library to use for PHP, but let's take this one for example: https://github.com/miladrahimi/php-jwt

On server A you generate the token:

// Use HS256 to generate and parse tokens
$signer = new HS256(JWT_SECRET_KEY);

// Generate a token
$generator = new Generator($signer);
$jwt = $generator->generate(['file' => 'bigfile.zip', 'ip' => $_SERVER['REMOTE_ADDR']);

In server B you validate and parse the token:

$jwt = &$_GET['token'];
$signer = new HS256(JWT_SECRET_KEY);

// Add Validation (Extend the DefaultValidator)
$validator = new DefaultValidator();
$validator->addRule('ip', new EqualsTo($_SERVER['REMOTE_ADDR']));

// Parse the token
$parser = new Parser($signer);

try {
    $claims = $parser->parse($jwt);
    // Serve $claims['file'] here.
} catch (ValidationException $e) {
    // Validation failed.
}

In both servers you sohould dedine the secret key:

define('JWT_SECRET_KEY', 'your-long-secret-key-here');

And of course, you can add more validation, token expiration, etc.

If you want to keep the private key on server A only, you should use the RSA algorithm, then you generate the token using your private key on server A, and parsing the token on server B using a public key. (There's an example in the library's README)

HTMHell
  • 5,761
  • 5
  • 37
  • 79