Signed URLs
A signed URL grants temporary read access to an object. The URL carries an HMAC signature; the server verifies it before serving the bytes.
Generate a signed URL
Section titled “Generate a signed URL”const url = storage.signUrl("avatar.png", { expiresInSec: 300, key: "my-signing-secret",});// /files/avatar.png?sig=...&exp=...Verify (handled by the server)
Section titled “Verify (handled by the server)”The dev server and StorageAdapter.verifySignedUrl check:
- the signature matches the key, and
- the expiry has not passed.
const ok = storage.verifySignedUrl(url, "my-signing-secret");Timing-safe verification
Section titled “Timing-safe verification”verifySignedUrl compares the HMAC with timingSafeEqual instead of a plain
===. A plain comparison would leak the signature one byte at a time under
careful timing measurement — timingSafeEqual closes that (hardening audit #3).
Real-world example
Section titled “Real-world example”// client requests a download linkapp.get("/download/:key", (req, res) => { const url = storage.signUrl(req.params.key, { expiresInSec: 60, key: process.env.SIGN_KEY!, }); res.json({ url });});Best practices
Section titled “Best practices”- Keep
expiresInSecshort (60–300s) for downloads. - Store the signing key in an env var, never in code.
- Rotate the key periodically; old URLs then expire on next verification.
Common mistakes
Section titled “Common mistakes”- Using a long expiry “for convenience” — widens the window if leaked.
- Hand-comparing signatures (timing attack).
Related
Section titled “Related”- Overview — storage concepts.
- S3 Adapter — signed URLs over S3.
