Skip to content

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.

const url = storage.signUrl("avatar.png", {
expiresInSec: 300,
key: "my-signing-secret",
});
// /files/avatar.png?sig=...&exp=...

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");

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).

// client requests a download link
app.get("/download/:key", (req, res) => {
const url = storage.signUrl(req.params.key, {
expiresInSec: 60,
key: process.env.SIGN_KEY!,
});
res.json({ url });
});
  • Keep expiresInSec short (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.
  • Using a long expiry “for convenience” — widens the window if leaked.
  • Hand-comparing signatures (timing attack).