Skip to main content

Command Palette

Search for a command to run...

A magic link is not a forever permission grant

Updated
•3 min read•View as Markdown
A
Practical lessons on auth, authorization, and access control.

Magic links prove the inbox at click time. They do not mean that user should still read, edit, or export that resource an hour later.

The bug pattern

A common flow:

  1. User requests access to a document, invite, or passwordless login.
  2. App emails a signed URL with a subject id, resource id, and expiry.
  3. The click handler validates the signature and TTL, then opens a session or returns the object.
  4. Later requests reuse that session (or a derived token) without re-checking whether the grant still holds.

Between send and click — or between first click and the next API call — a lot can change: membership revoked, invite cancelled, document moved to another tenant, or the original sharer loses permission to share. The link still opens because integrity and freshness are not the same thing.

Signing stops tampering. A short TTL stops infinite reuse of the URL. Neither freezes authorization.

  • A valid signature only says the bytes were not forged.
  • Expiry only says the delivery channel is still in window.
  • Many apps mint a long-lived session after one magic-link click; the URL TTL ends, but the privileged session does not.
  • "View once" UX often still leaves list/detail/export endpoints open for the new session.

Proof of inbox control a durable authorization decision.

What to enforce

Treat the magic link as a one-shot authentication or intent signal, then authorize normally:

  • Authorize on redeem. When the link is clicked, run the same policy you would for a logged-in user on that action and resource (tenant, role, relationship, object ACLs).
  • Authorize again on every later call. Do not promote the click into a blanket "can do anything this link hinted at" session.
  • Bind purpose tightly. A link for view:doc/123 must not become export:workspace/* because the session cookie is broad.
  • Revoke with the grant. If the invite, share, or membership is cancelled, redeem and follow-on access should fail closed even if the signed blob has not expired.
  • Prefer step-up over sticky privilege. After redeem, land in a normal session and re-check; avoid stuffing resource grants into the cookie.

Tiny checklist

  1. Does redeem run a real authorize(actor, action, resource), or only signature + TTL?
  2. After click, can a revoked member still hit detail/list/export with the new session?
  3. Can an invitee keep access after the sharer is removed from the tenant?
  4. Is the link's purpose (view vs edit vs join) enforced on every endpoint, not only the landing page?

Magic links are a delivery mechanism. Authorization still has to be live on redeem and on every request after.