All pages

Mastering Mixed Public & Private Files in Drupal 10 with S3fs (The "Private Bucket" Method)

Implementing a file system in Drupal 10 that securely handles sensitive documents while serving public images — all from a single, private AWS S3 bucket.

Summary

Implementing a file system in Drupal 10 that securely handles sensitive documents while serving public images — all from a single, Private AWS S3 bucket.

The Core Problem: The "View Media" Paradox

Drupal controls file access based on the Parent Entity, not the Field. Granting "View media" to Anonymous users automatically grants download access to ALL attached files — including private ones.

Technical Obstacles

  1. s3:// Protocol Gap — Private buckets return 403.
  2. Query Parameter Failures — triggers WAF LFI alerts.
  3. Routing Slash Problem — breaks Drupal's router.
  4. S3FS Reserved Keywordspublic/ or private/ throws CrossSchemeAccessException.
  5. Presigned URL Caching — unique signatures break CDN caching.

Solution: Base64-encode the S3 key: /s3-proxy/{base64_key}

Architecture

  • open/ folder → public access, Cache-Control: public
  • file-private/ folder → authenticated only, Cache-Control: private

Routing

[MODULE].s3_proxy:
  path: '/s3-proxy/{key}'
  requirements:
    _access: 'TRUE'
    key: .+

URL Rewriter

function mymodule_file_url_alter(&$uri) {
  $scheme = \Drupal::service('stream_wrapper_manager')->getScheme($uri);
  if ($scheme === 's3') {
    $target = \Drupal::service('stream_wrapper_manager')->getTarget($uri);
    $uri = Url::fromRoute('mymodule.s3_proxy', ['key' => base64_encode($target)])->toString();
  }
}
Mastering Mixed Public & Private Files in Drupal 10 with S3fs (The "Private Bucket" Method) | FlareCMS