Skip to content

Signed URLs

Signed URLs attach an HMAC-SHA256 signature (and an optional expiration timestamp) as query parameters to a URL. This lets you share links that cannot be tampered with — if anyone modifies the path, query string, or expiration, the signature check fails.

Common use cases include temporary download links, email verification, password reset flows, and shareable preview links.

Set an APP_SECRET environment variable in your Worker. This value is used as the HMAC signing key.

wrangler.toml
[vars]
APP_SECRET = "your-secret-key"

Call ctx.signedUrl() from any route handler to produce a signed link to a named route. The method accepts the route name, route parameters, and an options object.

import { Controller, Route, type RouterContext } from 'stratal/router'
@Controller('/files')
class FilesController {
@Route({ name: 'files.download' })
async show(ctx: RouterContext) {
const url = await ctx.signedUrl('files.download', { id: '123' }, {
expiresIn: 3600, // 1 hour
})
return ctx.json({ downloadUrl: url })
}
}
OptionTypeDescription
expiresInnumberTime in seconds until the signed URL expires. Omit for a non-expiring link.

Use ctx.hasValidSignature() to check whether the current request URL carries a valid, non-expired signature.

@Route({ name: 'files.download' })
async show(ctx: RouterContext) {
const valid = await ctx.hasValidSignature()
if (!valid) {
return ctx.json({ error: 'Invalid or expired link' }, 403)
}
// serve the file...
}

The method returns false if the signature is missing, does not match, or the link has expired.

If you need to sign or verify URLs outside of a route handler — for example in a queue consumer or scheduled task — use the standalone helpers.

import { signUrl, verifySignedUrl } from 'stratal/router'
const signed = await signUrl('https://example.com/download/123', secret, {
expiresIn: 3600,
})
const isValid = await verifySignedUrl(signed, secret)