Skip to main content

Signed URLs

url, err := store.SignedURL(ctx, "images/photo.jpg",
objstore.WithExpires(15 * time.Minute),
)

// Signed URL for upload
url, err := store.SignedURL(ctx, "uploads/new-file.jpg",
objstore.WithMethod("PUT"),
objstore.WithExpires(5 * time.Minute),
objstore.WithSignedContentType("image/jpeg"),
)

Cloud backends (s3, gcs, azure) presign natively against the provider's own signing scheme.

Local and memory​

The local and memory backends have no storage server of their own, so they produce HMAC-signed URLs that your application serves and verifies. Configure a signing secret and a base URL pointing at your own file-serving/upload handler:

store, _ := objstore.NewLocalStorage(
objstore.DefaultLocalConfig().
WithBasePath("./uploads").
WithBaseURL("https://api.example.com/files").
WithSigningSecret(os.Getenv("OBJSTORE_SIGNING_SECRET")),
)
// MemoryStorage: NewMemoryStorage().WithBaseURL(...).WithSigningSecret(...)

uploadURL, _ := store.SignedURL(ctx, "uploads/doc.pdf",
objstore.WithMethod("PUT"),
objstore.WithSignedContentType("application/pdf"),
objstore.WithExpires(15*time.Minute),
)

Your handler validates the URL it receives with VerifySignedURL:

req, err := objstore.VerifySignedURL(r.URL.String(), secret)
switch {
case errors.Is(err, objstore.ErrSignatureExpired): // 403, expired
case errors.Is(err, objstore.ErrSignatureInvalid): // 403, bad signature
case err == nil:
// req.Method / req.Path / req.ContentType are authorized; enforce them,
// then store.Put(...) the uploaded body (PUT) or store.Get(...) (GET).
}

The signature covers only the URL path, so passing r.URL.String() works as-is even though it carries no scheme/host. Note that req.Path includes any path prefix from BaseURL (e.g. a BaseURL of https://api.example.com/files yields a req.Path of /files/<object>); strip that prefix before passing the key to store.

Without a signing secret, SignedURL returns the unsigned public URL for GET and ErrNotImplemented for any other method (a local backend can't authorize a write without a secret). Both require a base URL, else ErrNotImplemented.