How to protect private uploads using signatures and rewrite in PHP in WordPress

Contents

Overview

This article shows a complete, production-ready approach to protecting private WordPress uploads by using signed URLs and rewrite rules that route requests to a PHP verifier/streamer. The pattern is simple:

  • Keep private files inaccessible via direct public URLs (either outside the webroot or in a protected folder).
  • Generate time-limited signed URLs (HMAC-based) or signed URLs tied to a user/session/permission.
  • Use webserver rewrites to route public-looking protected requests into a PHP handler (inside WordPress) that validates the signature, checks permissions, and streams the file (or delegates to X-Sendfile/X-Accel-Redirect).

Why this approach?

  • Signed URLs are stateless and can be generated for temporary external sharing without making files public.
  • Routing via rewrite lets you keep files physically inside wp-content/uploads or outside the webroot while preventing direct URL access.
  • PHP validation allows flexible permission checks (attach-to-post, owner-only, role checks, token-based sharing) and audit/logging.

Prerequisites and assumptions

  • You can add a plugin or mu-plugin to your WordPress site.
  • You can add rewrite rules to Apache (.htaccess) or Nginx config (or use index.php query routing).
  • You can configure server for X-Sendfile/X-Accel-Redirect for optimal performance (optional, but recommended for large files).
  • You will store or mark some uploads as private (either in a /private/ subfolder of uploads, or outside the webroot).

Design choices

There are two common storage approaches:

  • Private folder inside uploads (e.g., wp-content/uploads/private): easier to manage with WordPress media handling but must block direct HTTP access via .htaccess/Nginx rules.
  • Outside webroot (e.g., /var/www/private_uploads): safer by default, webserver cannot serve directly. Your PHP handler must reference filesystem paths outside uploads.

High-level flow

  1. When an attachment is private, generate a signed URL for it: e.g. https://example.com/protected/path/to/file.jpg?expires=TIMESTAMPsig=HMAC
  2. Client requests that URL.
  3. Webserver rewrite directs the request to WordPress index.php (or a plugin proxy). WordPress plugin inspects the incoming request.
  4. Plugin validates the signature and the expiration and optionally checks user permissions or association to a post or order.
  5. On successful validation, plugin streams the file with appropriate headers or hands off to Nginx/X-Sendfile for efficient serving.

Signing algorithm

Use HMAC (SHA-256) over a stable string that includes the file path (or attachment ID), expiration timestamp, and optionally user ID or a nonce. Keep the secret safely in wp-config.php or in the DB with strict permissions. Never reveal the secret.

Recommended data string

For predictable signatures use a canonical data string, for example:

  • data = relative_path expires attachment_id (if you want binding to ID)

Example: .htaccess rewrite for Apache

This sends requests under /protected/ to index.php with a query var protected_file. Place this in the WordPress root or the vhost config.

ltIfModule mod_rewrite.cgt
  RewriteEngine On
  # Send anything under /protected/ to WP for validation
  RewriteRule ^protected/(. ) /index.php?protected_file=1 [QSA,L]
lt/IfModulegt

Example: Nginx rewrite/location

For Nginx, route /protected/ to index.php and keep query args. Place inside your server block.

location ^~ /protected/ {
    # pass the request through to WordPress (index.php), preserving args
    try_files uri uri/ /index.php?args
}

Minimal WordPress plugin: core ideas

Below is a self-contained plugin skeleton showing:

  • Generating signed URLs for attachments
  • Adding a rewrite rule and query var protected_file
  • Validating signature and expiry
  • Permission check example (owner or capability)
  • Streaming with optional X-Sendfile/X-Accel support

Plugin file (single-file example)

lt?php
/
Plugin Name: Protected Uploads (Signed URLs)
Description: Protect uploads via signed URLs and rewrite to a PHP validator that streams files.
Version: 1.0
Author: Example
/

// CONFIG: define a secret key in wp-config.php
if ( ! defined( PROTECTED_UPLOADS_SECRET ) ) {
// fallback to an option (create and secure this in admin)
define( PROTECTED_UPLOADS_SECRET, get_option( protected_uploads_secret, change-me ) )
}

// Add rewrite rule on init
add_action( init, function() {
add_rewrite_rule( ^protected/(. )



Acepto donaciones de BAT's mediante el navegador Brave :)



Leave a Reply

Your email address will not be published. Required fields are marked *