Your organization issues thousands of pre-signed URLs per hour for direct-to-storage access using a single shared account-level signing key. Security now requires the ability to revoke access for a specific issued token within seconds if it's suspected leaked, without breaking every other outstanding URL. How would you redesign the token-issuing architecture to support that?
answer
- shared key = all-or-nothing revoke
- short TTL shrinks the need to revoke
- key group + edge deny-list = per-token kill
- stored access policy = per-policy, not per-token
basics
~20 sStop relying on one shared master key for everything. Instead, make each link individually trackable, for example by making tokens expire very fast, or by adding a lookup list of blocked token IDs that gets checked before the request reaches storage, so you can kill one without touching the rest.
solid answer
~40 sMove off raw account-key HMAC signing toward a scheme where each token is independently trackable: (a) issue short-TTL tokens, seconds to minutes, so leaked ones self-expire fast, reducing reliance on revocation; (b) use a signed-URL variant that supports server-side deny-lists, such as CloudFront signed URLs with key groups plus an edge check against a revocation list, or Azure SAS backed by a stored access policy per logical grant so that policy can be flipped off; (c) add a lightweight edge function that checks a fast revocation cache keyed by token ID before forwarding to storage, trading a bit of the fully-bypass-app-tier purity for real revocability. The core principle: individually addressable, short-lived grants beat one long-lived shared secret signing everything.
go deeper
Should understand at a high level that one shared key can't selectively revoke, only rotate everything.
Should suggest shortening token lifetimes as a partial mitigation to reduce reliance on revocation.
Should propose a concrete mechanism like a deny-list check or stored access policies, and understand its latency and availability cost.
Should design the end-to-end architecture, weigh fail-open versus fail-closed for the revocation cache, address deny-list growth and pruning, and generalize the lesson to other shared-secret systems.
## Why one shared key gives you all-or-nothing revocation A single shared account-level signing key means every pre-signed URL or SAS token issued from that account is validated the exact same way, by recomputing a signature using that one key. That design has an inherent, structural limitation for revocation: the storage service has no concept of this particular token at validation time, only whether the signature matches the key. The only lever that touches all tokens is the key itself, so the only native way to kill one leaked token is to rotate the shared key, which invalidates every other outstanding token issued from it in the same stroke, collateral damage disproportionate to a single leak, and disruptive enough that teams are reluctant to do it routinely, which in practice means leaked tokens often just get left to expire naturally. ## The three mechanisms that combine Redesigning for real per-token revocability means moving away from one long-lived secret signing everything, validated purely by math, toward each grant being either short-lived enough that revocation barely matters, or **individually addressable** so it can be selectively killed. Several concrete mechanisms combine to get there. 1. **First, shrink token TTLs aggressively**, seconds to a few minutes rather than hours, for any token backing a sensitive or high-value operation; the shorter the window, the less a revocation mechanism has to do, because most leaks are only dangerous for as long as the token remains valid anyway. 2. **Second, adopt a signed-URL variant that supports server-side, per-grant control** rather than pure client-side math. AWS CloudFront signed URLs or signed cookies issued through a key group are a good example: CloudFront still validates a cryptographic signature, but teams commonly combine this with a WAF rule or an edge function that checks an incoming request's token identifier against a fast, centrally updated deny-list before allowing it through, turning revocation into adding one ID to a blocklist rather than rotating a master key. 3. **Third, Azure's stored access policies** offer a related but coarser-grained middle ground: instead of embedding permissions and expiry directly in each SAS token's signature, the SAS references a named policy stored on the container, and because the policy's parameters live server-side, revoking access for every token issued against that policy is just editing or deleting the policy, no account-key rotation required, though this only gives per-policy granularity, commonly capped at a small number of stored policies per container, not true per-token granularity, so tokens would be grouped into policies by risk tier or logical grant rather than one policy per token. ## The architecture that actually gets there The architecture that actually achieves near-real-time single-token revocation typically adds a thin **edge or proxy layer** back in front of storage, for example an edge function that, before forwarding a validated request on to the storage backend, checks a fast revocation cache, commonly Redis or a similar low-latency store, keyed by a token ID embedded in the signed URL. If the ID is present in the revocation cache, the request is rejected regardless of whether the underlying signature is still cryptographically valid. This is an explicit and honest trade-off: - It **reintroduces a component** between the client and storage, adding a small amount of latency and a new availability dependency, since if the revocation cache is unreachable you must decide whether to fail open, allowing the request and weakening the guarantee, or fail closed, rejecting it and turning a cache outage into an availability incident for all uploads. - **In exchange**, you get the property that mattered: an individual leaked token can be neutralized within the cache's propagation delay, typically well under a second to a few seconds, instead of waiting for natural expiry or accepting collateral damage from a full key rotation. ## The failure mode to watch for The failure mode to watch for once this is built is **revocation-list staleness or unbounded growth**: if entries aren't pruned once their underlying token would have expired anyway, the deny-list grows forever and lookup latency creeps up; a correct design expires deny-list entries at the same time the token itself would have expired, since after that point the signature check alone already rejects it. ## Where it shows up, and the general lesson A concrete real-world instance of this general shape is **CloudFront's signed URLs with key groups** combined with an edge-executed revocation check, used by media-streaming platforms to be able to kill an individual leaked video-segment URL within seconds without disrupting every other viewer's active session. The underlying architectural lesson generalizes well beyond storage: any system relying on one long-lived shared secret to validate many independent grants faces the same all-or-nothing revocation ceiling, and the fix is always some version of making grants either short enough that revocation rarely matters, or individually addressable so a single one can be pulled without collateral damage to the rest.
- Why not just rotate the account key immediately when you suspect one token leaked?Because rotating the shared master key invalidates every outstanding valid token at once, causing wide collateral disruption disproportionate to a single leak. It's a reasonable break-glass measure for a suspected key compromise, but far too blunt to use as routine per-token revocation.
- What's the trade-off of adding an edge revocation-check function in front of storage?It adds latency and infrastructure, a small compute cost per request, plus dependency on the revocation cache's availability, and reintroduces a component between client and storage, partially undoing the bypass-the-app-tier benefit. In exchange it buys near-real-time single-token revocation, which pure signature validation can't offer.
- How do stored access policies in Azure SAS give a middle ground here?SAS tokens reference a policy ID stored on the container instead of embedding permissions in the token itself, so revoking is just deleting or editing that container-side policy. But it groups tokens by policy rather than truly per-token, so granularity is limited to how many distinct policies you're willing to manage, which Azure caps at a small number per container.
Like a company that hands out master keys copied from one blank instead of individually coded keycards: if one employee's key is stolen, the only fix is rekeying every lock in the building, locking out everyone else's key too, versus a keycard system where security can deactivate just that one badge instantly from a console.
saying these in an interview costs you the question
- Proposes rotating the shared key as routine single-token revocation
- Doesn't recognize the fundamental tension between true bypass and per-token revocability
- No mention of short TTL as a primary mitigation
- Unaware of stored access policies or signed-cookie-with-key-group style mechanisms
- Assumes a revocation cache can never fail and skips the fail-open versus fail-closed decision