rekalogika/temporary-url-bundle
rekalogika/temporary-url-bundle generates secure, time-limited URLs for your resources, so shared files and actions expire automatically. Configure lifetimes, sign requests, and integrate with your framework's routing to protect downloads with minimal setup.
First, a framing note: rekalogika/temporary-url-bundle is a Symfony bundle, not a Laravel package. If you are working in a Laravel codebase, you almost certainly want the built-in URL::temporarySignedRoute() / signed middleware instead — installing this means adding Symfony's dependency-injection, routing, and (optionally) Doctrine layers. The realistic use cases for a Laravel developer are (a) maintaining a Symfony service that shares your domain model, or (b) extracting a framework-agnostic signing concept into a shared package.
Assuming a Symfony application:
composer require rekalogika/temporary-url-bundleconfig/bundles.php and import the bundle routes (1.8.1 ships these as PHP routes — see Gotchas).config/packages/rekalogika_temporary_url.yaml; keep it in .env, not in VCS.TemporaryUrlGeneratorInterface, usually autowired) and the interface an entity implements to declare how its URL is built (TemporaryUrlableInterface — confirm the exact FQCN against the installed version).First use case: an entity such as an Invoice or File implements the temporary-URL interface; a controller or command calls the generator with an expiry, producing a signed, absolute URL; a public controller action validates it and streams the file. That is the whole loop — generate, distribute, validate, serve.
Version note: 1.8.1 is a patch release with no API changes, so the upgrade is drop-in. There is nothing new to learn; treat it as a routing-config bugfix.
Generator at the edge, validator at the route. Keep URL generation in the service/command layer (so it can be called from queue workers and mailers) and validation entirely in the consuming controller. Never hand-roll the signature.
Entity-driven signing. Implementing the interface on the entity keeps the URL shape next to the domain object and makes generation a one-liner: generate($invoice, new \DateTimeImmutable('+15 minutes'), ['action' => 'download']). Extra parameters passed here become part of the signature, so a link minted for download cannot be replayed against delete — use this instead of separate routes where the action is a variable.
Short TTLs plus a revocation escape hatch. Signed URLs are stateless and therefore unrevocable until expiry. The practical pattern is a 5–60 minute TTL and a version/nonce column on the entity that participates in the signature; bump it to revoke. Rotating the app key revokes everything at once — useful as a panic button, catastrophic as a routine operation.
Batch distribution. Emails with per-recipient download links are the canonical workload: generate inside a queued job, one URL per entity, and make sure the job has an HTTP host — see the default_uri gotcha below.
Composition with authorization. The bundle proves the URL was issued by you, not the caller is allowed. If the target depends on tenant or role, either encode the tenant in the signed parameters or perform the authorization check inside the consuming action from the entity's own state.
Framework-agnostic porting. If you are translating the idea to Laravel: URL::temporarySignedRoute('download', now()->addMinutes(15), ['invoice' => $id]) and the signed middleware are the direct equivalents; validation is $request->hasValidSignature(). Keep the same discipline — short TTL, signed action parameter, nonce for revocation.
1.8.1 is a routing-loader fix. The bundle converted routes.xml to routes.php. If you were on 1.8.0 and hit route-loading failures (notably on newer Symfony versions where the XML route loader is deprecated or absent), 1.8.1 is the fix. After upgrading, run bin/console cache:clear — routing is cached aggressively and a stale cache masks the change. If you copied or overrode the old routes.xml resource path in your own config, repoint it; if you imported the bundle routes via the standard resource name, no action is needed.
Host and scheme in CLI/queue contexts. Outside a request there is no Host header, so the router falls back to framework.router.default_uri (or localhost). Set it explicitly in production, or your queued emails will contain http://localhost/... links. Behind a load balancer, configure TRUSTED_PROXIES or every generated URL will be http:// and silently wrong.
Signed, not encrypted. Parameters are base64-visible in the URL. Never place personal data, tokens, or internal file paths in them — only opaque identifiers and the action name.
Clock skew. Expiry is validated against the verifying server's clock. A few seconds of drift between web and worker nodes is enough to produce intermittent "link expired" reports that never reproduce locally. Use short TTLs but not razor-thin ones, and consider a validation leeway if the bundle exposes it.
Debugging checklist, in order: decode the query string and confirm expires is in the future; confirm the signature key is the same across all app instances (a partially-rotated key is the single most common cause of "works on one node"); confirm the parameter order/canonicalization matches what the generator produced; confirm the route name and host match. If you need custom signature logic or a different route shape, the extension point is the signature generator / URL builder service — override it in services.yaml rather than wrapping the generator, so validators and generators stay in agreement.
Upgrade policy. Track the changelog for major versions before bumping; 1.x patches like 1.8.1 are safe. Since the bundle sits on Symfony routing internals, it is the kind of dependency that breaks on framework majors rather than on its own minors — test it when you bump Symfony, not when you bump this.
How can I help you explore Laravel packages today?