Signed URLs
A private file cannot be put in an <img src>, mailed to a customer, or handed to a third-party renderer: none of those carry your session cookie. Making the bucket public to solve that trades a scoped problem for an unscoped one.
A signed URL is the middle path. It is an ordinary URL carrying an HMAC-SHA256 token that binds the bucket, the path, the operation and an absolute expiry into one signature. Change any of those four in the query string and the signature stops matching, so the token grants exactly one file, for one purpose, until one moment — and nothing else.
The signing key is AUTH_SECRET — or, when you have not set one, a value derived from the app's encryption key. Changing either invalidates every outstanding signed URL at once, which is the intended emergency lever.
Public vs Private Access
Before reaching for a signature, know which files need one. Three settings decide, from broadest to narrowest:
| Setting | Scope | Effect |
|---|---|---|
STORAGE_DEFAULT_ACCESS=public |
Every file | Nothing is private. Signing becomes pointless. |
STORAGE_PUBLIC_PATHS |
Key prefixes | Files under a listed prefix are served with no session or token. |
public: true on a bucket |
One bucket | That bucket's files are served with no session or token. |
STORAGE_DEFAULT_ACCESS=private
STORAGE_PUBLIC_PATHS=assets/,avatars/,logos/Prefixes are matched literally against the storage key, so avatars/ covers avatars/me.png and nothing else. Wildcards are rejected at startup rather than silently never matching — a * in the value is almost always a mistake about the semantics, and failing loudly beats a prefix that quietly protects nothing.
Everything outside those three carve-outs answers 404 to an anonymous request. That is where signing earns its place.
Download Signed URLs
Moved to Download URLs — minting a read token and its expiry window.
Upload Signed URLs
Moved to Upload & Batch Signing — direct-from-browser uploads with token-bound constraints.
Batch Signing
Moved to Upload & Batch Signing — up to 100 URLs in one round trip.
Endpoint Summary
| Method | Endpoint | Authentication |
|---|---|---|
POST |
/api/buckets/{bucket}/sign |
Session required. |
POST |
/api/buckets/{bucket}/sign/batch |
Session required. |
GET |
/api/buckets/{bucket}/signed?path=…&… |
None — the token is the credential. |
PUT |
/api/buckets/{bucket}/signed?path=…&… |
None — the token is the credential. |
Note the shape: signing and using are different routes. You mint against /sign with a session, and the resulting URL points at /signed, which needs nothing. That is what makes the URL forwardable.
Access Control
Signing is gated by the bucket's sign and signUpload permissions:
| Caller | Answer |
|---|---|
| No session | 401 |
| Session, role matches the permission | The signed URL. |
| Session, role does not match | 404 — the boundary itself stays invisible. |
admin |
Always passes, whatever the permission says. |
Bucket declares no permissions block |
Admin only. This is the default, and it is strict. |
sign: all does not mean anonymous. The session check runs first, before any permission is evaluated, so all widens signing to every logged-in user rather than to the public. If you want files reachable with no session at all, that is public: true on the bucket or a STORAGE_PUBLIC_PATHS prefix — not a signing permission.
Attachment Field Integration
Moved to Attachment Fields & Storage — how a record's attachment arrives already signed.
Related Pages
- Download URLs — read tokens and expiry.
- Upload & Batch Signing — write tokens and bulk minting.
- Bucket Permissions —
signandsignUpload. - Storage Backends — S3 presigning versus internal HMAC.
- File Operations — the session-based alternative.
- Environment Variables: Services — public-path configuration.
Last updated August 11, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta. Contributions and corrections are welcome.