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
- When an attachment is private, generate a signed URL for it: e.g. https://example.com/protected/path/to/file.jpg?expires=TIMESTAMPsig=HMAC
- Client requests that URL.
- Webserver rewrite directs the request to WordPress index.php (or a plugin proxy). WordPress plugin inspects the incoming request.
- Plugin validates the signature and the expiration and optionally checks user permissions or association to a post or order.
- 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 :) |