CodeKits.shopNew developer offer

Revoke temporary links and understand the limits

Implement server-side link revocation with owner-scoped updates, explicit cache policy and a clear distinction between future grants and existing copies.

By Jonathan · 4 min read

Published

Owner revokes, Fresh checks fail, and Existing copies remain
In this article

Revocation means refusing future access under a credential. It does not mean retrieving every downloaded copy, clearing browser screenshots or proving that backups have disappeared. A useful interface makes that boundary visible before the owner clicks Revoke.

This walkthrough extends a database-backed temporary-link service. It targets MySQL 8.4 and a Next.js server-side authorization boundary. The statements are integration excerpts; no live revocation or CDN test was performed in this environment.

Keep a revocable server record

A completely self-contained signed token may remain valid until its expiry unless the server also checks a revocation record or version. If immediate future-request revocation matters, keep a database record that every grant resolves.

A link row can contain its owner, token hash, file ID, expiry, revoked timestamp and policy version. The raw token should remain a bearer secret; the revocation interface should address the internal link ID under the authenticated owner's account.

revoke.sql — bind verified owner and internal link ID
UPDATE download_links
SET revoked_at = COALESCE(revoked_at, UTC_TIMESTAMP(6))
WHERE id = ?
  AND owner_id = ?;

The owner ID comes from the verified server session. Do not accept it from the request body. Add tenant scope if the application has multiple tenants. CSRF protection and an audit event belong around this state-changing operation.

Repeating the same revocation should be harmless. A second request does not need to create another transition or return a confusing error. When reporting success, distinguish an authorized already-revoked record from a nonexistent or unauthorized target according to your disclosure policy.

Check revocation before every new grant

The download lookup must include revoked_at IS NULL and the expiry predicate. Do not merely hide the link from a dashboard.

If link issuance or counting uses a transaction, define the order between its grant decision and revocation. A row lock can serialize those decisions: a grant committed first may proceed, while a revocation committed first prevents the next grant. The user-facing promise should be “new access requests are refused after revocation takes effect,” not “all bytes instantly stop.”

Long-running streams already authorized are a separate concern. Interrupting them requires tracking and cancelling active streams, and it still cannot recall bytes already delivered.

Account for caches and redirects

A CDN must not serve protected content without performing the intended authorization check. Private no-store responses simplify the model, but verify the actual deployment rather than assuming the header reaches every layer.

A redirect to an object-storage presigned URL creates another credential with its own expiry. Revoking the original application link may not invalidate that URL. If you need tighter revocation, use short-lived downstream credentials or proxy delivery through a service that checks each request.

Do not promise instantaneous revocation from an asynchronously replicated database unless your consistency design supports it. Reading revocation state from a lagging replica can temporarily grant access after the primary accepted the change.

Avoid accidental resurrection

Revocation should normally be terminal for that token. “Share again” should create a fresh token, not clear revoked_at on the old record. Old copies of the link then stay invalid.

Restoring a database backup can resurrect revoked records if newer revocations are absent from the backup. Include revocation state in recovery planning. Depending on risk, use a separately retained revocation journal or invalidate all old link generations after recovery.

Keep the owner-facing event history small and useful: who revoked which internal link ID, when and whether it succeeded. Do not log the credential itself.

Test the real delivery path

Create a synthetic link and confirm it works. Revoke it, then retry from a fresh browser session, an existing tab, a direct HTTP client and any previously issued storage URL. Verify the exact behaviour of requests that began before revocation.

Test repeated revocation, revocation by another account, expired links and concurrent download/revoke requests. Exercise a recovery restore in staging and check that retired tokens do not unexpectedly work.

OWASP's authorization guidance supports checking current permission at each server boundary; the downstream URL and backup tests are additional design consequences of this particular workflow. Authorization guidance.

If access persists, inspect cached responses, replicas and secondary credentials before changing token length. For a related expiry model, see expiring links with MySQL. CodeKits can build a temporary-access workflow with clearly stated limits.