A Plug that serves local-disk media files with optional Content-Disposition
and HMAC-signed URL verification.
Quick Start
Mount the plug in your Phoenix router at the same path you configured as
download_base_url in the disk config:
# router.ex
forward "/media", PhxMediaLibrary.Plug.MediaDownload, disk: :local
# config.exs
config :phx_media_library,
disks: [
local: [
adapter: PhxMediaLibrary.Storage.Disk,
root: "priv/static/uploads",
base_url: "/uploads",
download_base_url: "/media",
secret_key_base: "long-random-secret-at-least-32-bytes"
]
]Generating Download / Signed URLs
Use the top-level helpers in PhxMediaLibrary:
# Content-Disposition download link (no signing required for public media)
href = PhxMediaLibrary.download_url(media)
# HMAC-signed URL (expires in 1 hour by default)
href = PhxMediaLibrary.signed_url(media)
# Signed *and* downloadable
href = PhxMediaLibrary.signed_url(media, nil, download: true, expires_in: 300)URL Formats
Unsigned download (Content-Disposition only):
GET /media/images/1/uuid/photo.jpg?dl=1Signed (no Content-Disposition):
GET /media/images/1/uuid/photo.jpg?sign=<token>&exp=<unix_ts>Signed download:
GET /media/images/1/uuid/photo.jpg?sign=<token>&exp=<unix_ts>&dl=1When a sign parameter is present the plug always verifies the token
before serving the file. If the token is missing but the URL was generated
with signed: true, verification fails with 403 Forbidden.
Plug Options
:disk— (required) the disk name atom (e.g.:local) to look up in the PhxMediaLibrary disk config. The disk must usePhxMediaLibrary.Storage.Diskas its adapter.:require_signed— whentrue, every request must carry a validsign+exppair. Unsigned requests return403 Forbidden. Defaults tofalse, which allows unsigned requests (no token required for plain download links).
Security Notes
- Without
:require_signed, any file under the configured:rootdirectory can be downloaded via this plug. Use Phoenix's existing auth pipeline (:require_authenticated_user, etc.) to restrict access. - The HMAC secret (
secret_key_base) should be at least 32 bytes of cryptographically random data. Do not reuse your Phoenixsecret_key_basefor this purpose in production.