Skip to main content
View as Markdown

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.
>_ terminal
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.

Attachment Field Integration

Moved to Attachment Fields & Storage — how a record's attachment arrives already signed.

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.

Built with Sovrium