Download Security

LicenceForge protects product file downloads with short-lived, cryptographically signed tokens. This ensures that only authenticated license holders can download files, and that download links cannot be shared or reused after expiry.

Token Generation

Download tokens are generated by the WPLF_Crypto::generate_download_token() method. The method accepts three parameters:

Parameter Type Description
$license_id int The ID of the license requesting the download.
$product_slug string The slug of the product to download.
$expires_in int Token lifetime in seconds. Default: 300 (5 minutes).
// Generate a download token with default 5-minute expiry
$token = WPLF_Crypto::generate_download_token( $license_id, 'my-plugin', 300 );

Token Format

The token payload is a JSON object containing the license ID, product slug, and expiry timestamp. This payload is signed with HMAC-SHA256 using the WordPress AUTH_KEY and AUTH_SALT as the signing key. The signed payload is then base64url-encoded for safe inclusion in URLs.

{
  "license_id": 42,
  "product_slug": "my-plugin",
  "expires": 1706900400
}

base64url encoding replaces + with - and / with _, and omits padding characters. This ensures tokens are safe for use in URL query parameters without additional encoding.

Token Verification

When a download request arrives, WPLF_Crypto::verify_download_token() performs two checks:

  1. Signature verification — The HMAC-SHA256 signature is recomputed and compared using hash_equals() to prevent timing attacks. If the signature does not match, the token is rejected.
  2. Expiry check — The expires timestamp is compared against the current server time. If the token has expired, the request is denied with an appropriate error.
// Verify a download token
$payload = WPLF_Crypto::verify_download_token( $token );

if ( is_wp_error( $payload ) ) {
    // Token is invalid or expired
    wp_send_json_error( $payload->get_error_message(), 403 );
}

// $payload contains license_id, product_slug, expires

Download Endpoint

The REST API endpoint for file downloads is:

GET /wp-json/wplf/v1/downloads/{product_slug}?token={download_token}

Request Flow

  1. Customer clicks the download link in the customer portal or email.
  2. The front end requests a fresh download token from the server.
  3. The server generates a token bound to the customer's license and the requested product.
  4. The customer's browser is redirected to the download endpoint with the token as a query parameter.
  5. The endpoint verifies the token, resolves the file source, and streams the file to the customer.
Download request flow diagram

File Sources

LicenceForge supports three file source types for product downloads. The source is configured per product in the admin panel.

Local Files

Files stored in the WordPress uploads directory or a custom assets path. The download endpoint reads the file and streams it to the client with appropriate headers (Content-Disposition: attachment).

External URLs

Files hosted on an external server. The endpoint redirects the customer to the external URL. The token is still verified before the redirect occurs.

S3 Pre-signed URLs

For files stored in Amazon S3 (or S3-compatible storage), LicenceForge generates a pre-signed URL with its own expiry. The customer is redirected to this URL, which grants temporary access to the S3 object.

S3 URL Expiry

The pre-signed URL expiry is controlled by the wplf_s3_url_expiry filter:

Setting Value
Default expiry 300 seconds (5 minutes)
Minimum expiry 60 seconds
Maximum expiry 3600 seconds (1 hour)
// Customise S3 pre-signed URL expiry (e.g., 10 minutes)
add_filter( 'wplf_s3_url_expiry', function( $seconds ) {
    return 600;
} );

Keep expiry times short. Longer expiry windows increase the risk of URL sharing. The default of 300 seconds is suitable for most use cases.

Direct Access Protection

To prevent direct access to product ZIP files stored in the local assets directory, LicenceForge includes an .htaccess file that blocks all direct HTTP requests to ZIP files.

# .htaccess in the LicenceForge assets directory
<FilesMatch "\.zip$">
    Order Allow,Deny
    Deny from all
</FilesMatch>

Nginx users. The .htaccess rule only applies to Apache. If you use Nginx, add an equivalent rule to your server configuration:

location ~* /wp-content/uploads/licenceforge/.*\.zip$ {
    deny all;
    return 403;
}

Token Expiry Defaults

Token Type Default Expiry Configurable
Download token 300 seconds (5 minutes) Via $expires_in parameter
S3 pre-signed URL 300 seconds (5 minutes) Via wplf_s3_url_expiry filter (60–3600s)

S3 storage recommended. For maximum security, store product files in S3 rather than the local filesystem. This removes the dependency on .htaccess rules and benefits from S3's own access control and audit logging. See Best Practices for more details.