Summary#
File.SignedUrl(path) returns a URL a browser can use to fetch a private app file — a file stored under any
path other than public/. The URL carries a short-lived, cryptographically signed token, so it works for anyone who
holds the link until it expires, and 404s otherwise. Use it for a per-user report, an invoice PDF, or a private image:
string url = File.SignedUrl("reports/" + user.Id + "/q3.pdf");Like the rest of the File.* surface it is gated on using Osyrin.Storage;.
Signature#
using Osyrin.Storage;
string File.SignedUrl(string path)Description#
The app-file serving route treats public/ and everything else very differently. A file under public/ is served to
anyone, anonymously, and its URL is just File.Url. A file under any other path is private: the
serving route refuses it outright — until the request carries a valid signed token. File.SignedUrl mints that token.
Can I put it straight in a render argument?#
This is the key difference from File.Url. File.Url is pure string formatting, so it can sit directly in a
render argument (Image(src: File.Url(...))) and be built by the browser. File.SignedUrl signs with your app's
key, which the browser never holds — so it runs on the server, inside a function or action. Call it where you have
the path in hand (an action that prepares a download, a function that returns a link) and hand the result to the UI.
Who can use the link, and for how long?#
A signed URL is a bearer link: whoever holds it can fetch that one file until it expires. That is exactly what lets
a browser <img> or a download load without a login header. Two properties keep it safe:
- It expires. The link is valid for a short window (currently 15 minutes), then stops working. Mint it when the page or download is requested, not far in advance.
- It unlocks exactly one file, for one app. The path and your application are sealed inside the token, so a link
minted for
reports/a.pdfcannot be edited to fetchreports/b.pdf, and a link from one app is meaningless on another. A tampered or expired link simply 404s — a private file's existence is never revealed to someone without a valid link.
You never see, choose, or store a key — the platform mints, protects, and rotates it, exactly as for Crypto.Encrypt and Crypto.Decrypt.
File.SignedUrl is signing, not a storage read: it does not check that the file exists (a missing file 404s when
the browser follows the link), and it is not available inside a query.
Examples#
An action returns a private, expiring download link for the caller's own report:
using Osyrin.Storage;
[Principal] entity User { [MaxLength(200)] string Email; }
// The report was written earlier under a NON-public path, so it is not servable anonymously.
string ReportDownloadUrl(User user) {
return File.SignedUrl("reports/" + user.Id + "/q3.pdf");
}Writing a private file, then handing back a link to it:
using Osyrin.Storage;
string SaveAndLinkInvoice(string invoiceId, byte[] pdf) {
var path = "invoices/" + invoiceId + ".pdf"; // NOT under public/ — private by default
File.WriteAllBytes(path, pdf);
return File.SignedUrl(path); // a temporary link the browser can open
}See also#
- File.Url — the public counterpart: a plain, permanent URL for a
public/file - Crypto.Encrypt and Crypto.Decrypt — the same "the platform owns your key" model, for encrypting stored values