Plan · 2026-05-21 · Both spikes cleared

Magic-Link Portal Login

One tap on a refill-reminder text or email and the patient lands in the refill wizard already logged in — no DOB entry, no verification code. Azure runs the bridge; Salesforce serves the wizard.

Spikes cleared · Build-ready (pending SFMC rotation) Azure-led One-click UX 72-hour TTL
~1
Salesforce metadata delivery
External Client App (+ maybe 1 permset)
72 h
Link lifetime
Multi-use within window
~$250 / mo
Estimated run cost
Front Door Standard + EP1

§1The feature, in one paragraph

When SFMC sends a refill-reminder text or email, the message carries a short URL. The patient taps it once and lands inside the Salesforce refill wizard already logged in — no DOB lookup, no verification code, no password. Three systems each do one thing: SFMC handles the send (hooks into the existing refill-reminder pipeline — no new flow); Azure mints the link, holds the signing key, and runs the OAuth handshake at click time; Salesforce establishes the session and serves the wizard.

The bulk of the build lives in the existing AdvancedRx Azure VNet — the same one that already hosts eFax, the e-script integration, and the patient-matcher service. The Salesforce footprint stays small; §4 lists exactly what's required there.

§2How the bridge works

SFMC sends the message; Azure mints the link, holds the signing key, and runs the OAuth handshake; Salesforce establishes the session and serves the wizard. Three systems, one job each.

Send
SFMC
Refill reminder
SMS or email
Bridge
Azure Function
Mint · Sign JWT · Exchange
VNet · Key Vault · SQL
Session
Salesforce
singleaccess
→ Refill wizard

Step by step

SFMC decides it's time to remind
The existing JBSystemFlow_Refill_Reminder_c picks the patient and selects channel (SMS or email).
SFMC asks Azure for a link
At template render, AMPScript HTTPPost2 hits the Azure mint endpoint with the patient's Contact Id, channel, and an idempotency key.
Azure mints + stores
Generates a 12-char base62 short code, stores the HMAC hash in SQL with a 72-hour TTL, returns the short URL to SFMC.
SFMC sends the message
The text or email goes out with the link https://login.advancedrx.net/r/Kj3pQ9xMv7nR.
Patient taps the link
Browser hits Azure (one tap, no interstitial — see §5). Azure looks up the hashed code, resolves Contact → Salesforce username via the local cache.
Azure signs the Salesforce JWT
ECA private key never leaves Key Vault — CryptographyClient.SignAsync handles the signature in-vault.
JWT Bearer → access token → frontdoor
Azure POSTs the assertion to Salesforce's token endpoint, gets an access token, swaps it at /singleaccess for a one-time-use redirect URL.
Patient lands logged in
303 redirect to the Salesforce frontdoor URI; Salesforce establishes the session and serves the refill wizard. Total time: under a second.

§3Why Azure

AdvancedRx already operates a HIPAA-compliant Azure VNet for eFax, e-script, and patient-matcher. Reusing it for magic-link gives wins that are hard to get inside Salesforce.

§4Salesforce-side surface

Because the link expires by time rather than by event, the Salesforce footprint is small and well-bounded. Two items max:

1
External Client App — the trust record
Holds Azure's public signing certificate, the OAuth scopes Azure can request, the "admin pre-authorized users" policy bound to the Customer Community Plus Login profile, and IP relaxation (clicks come from arbitrary patient IPs). In source-format metadata this is one ECA file plus its policy file; logically one deliverable.
+0 or 1
Permission set for the ADF integration user (if needed)
Azure's nightly ADF job extends to mirror portal User records alongside the existing Contact cache. If the integration user already has User-read access, no change. If not, one small permset grants it. To confirm pre-build.

Explicitly not in scope on the Salesforce side: Apex, Named Credentials, flows, community-reachable endpoints, Custom Objects, Big Objects, LWC, public Experience Cloud routes, CMDT allowlists, AMPScript JWT minting.

§5Key design decisions

Three calls shape the architecture. Each was evaluated explicitly against alternatives; each is reversible if practice proves the trade wrong.

5.1 — Time-based expiry vs event-based
The link can either expire on a clock (time-based) or be killed the moment the patient submits a refill (event-based via a Salesforce → Azure webhook). Event-based gives tighter kill precision but requires Apex callout wiring, a Named Credential, an Azure endpoint, and tests on both sides.
✓ Chosen
Time only — 72-hour TTL
Link expires by clock, not by event. Bounded blast radius (refills of existing prescriptions to saved address only) accepted. Keeps the Salesforce-side surface to a single metadata record.
Deferred
Webhook on completion
Tighter kill precision (seconds, not hours). Engineering cost: one Apex class + Named Credential + Azure endpoint + trigger-event wiring + tests on both sides.
5.2 — One-click vs interstitial
Strictest posture: a "Tap to continue" interstitial page that blocks link scanners from triggering Salesforce sessions inside their sandboxes. Industry norm (Slack, Substack, Medium): one click, accept that scanners briefly hold ephemeral sessions.
✓ Chosen
One click — no interstitial
Matches Slack/Substack-grade UX. Scanner-induced sessions live only inside the scanner's ephemeral HTTP client and self-destruct. Audit log flags ScannerSuspected via UA signatures so triage stays clean.
Deferred
Explicit "Tap to continue"
Strictest scanner protection. One extra tap on the patient's side. Reversible to this design in a single-handler change if Salesforce-API volume from scanners proves material at rollout.
5.3 — Front Door tier: Standard vs Premium
Front Door Premium adds managed WAF rule sets, Bot Manager, and Private Link to origin — $295/mo more than Standard. For one narrow magic-link route, custom WAF rules + good function-side validation get most of the value.
✓ Chosen
Standard — ~$35/mo
Custom WAF rules + per-IP rate limiting + TLS + custom domain + edge logging. Sufficient for a single narrow public endpoint with strict input validation behind it.
Deferred
Premium — ~$330/mo
Worth it if/when we put other public Azure surfaces behind the same edge (storefront, outbound fax intake, future patient-facing endpoints). Then $295/mo delta amortizes across the platform.

§6Two pre-build spikes (gates)

The architecture depends on two things working that aren't documented for our exact configuration. Both spikes are small, parallelizable, and gating — neither requires a single line of production code.

✓ Spike A · Salesforce · PASSED

JWT Bearer + singleaccess for CCP Login

Proved the full handshake works for a Customer Community Plus Login license user. Minted a JWT locally, exchanged at the community token endpoint (returned access_token + community URL), POSTed to /singleaccess (returned frontdoor_uri JSON), followed the redirect chain (sid session cookie set, HTTP 200 on the storefront page).
Verified 2026-05-18 in ClaudeTest · three pre-auth gotchas captured in plan.md Phase 0 + a new Phase 4 SetupEntityAccess wiring step.
✓ Spike B · SFMC · SOFT-PASSED

Link wrapping + fragment behavior

Production SFMC self-send delivered both fragment (`/r#…`) and path (`/r/…`) URLs to Gmail with the href values completely unmodified — no wrapping through click.advancedrx.email. Caveat: SFMC Test Sends often skip click-tracking link wrapping; real journey/triggered-send wrapping behavior deferred to Phase 6a A/B rollout for definitive verification. V2 design accommodates either outcome.
Verified 2026-05-21 via production self-send · four execution gotchas + AdvancedRx SFMC infrastructure facts captured in plan.md.

§7Cost

Order-of-magnitude monthly estimate at expected refill-reminder volume. SQL adds are near-zero (reuses existing DB); Key Vault and App Insights are small even at scale.

Front Door Standard
Custom WAF rules + TLS + custom domain + edge logging
~$35
Function App EP1
Dedicated, always-warm, isolated blast radius from eFax
~$150–250
SQL · reuse existing
Two new tables on existing DB
~$0
Key Vault signing ops
Per-click sign call; volume-priced
~$5
App Insights
Sampled + scrubbed
~$5–25
Total · estimated
Realistic v1 range
~$200–315 / mo

§8What's next

Both gating spikes are cleared. The remaining unblockers are out-of-band coordination items.

If Spike A fails, an alternative Salesforce-only architecture is available as a fallback design record at ../magic-link-portal-login.md.

§9Verification basis

The architecture is reviewed against the existing Azure infrastructure repo and the live Salesforce + SFMC landscape.

Azure side: Codex audit conducted 2026-05-18 against advancedrx-document-integration — the live eFax Function App, e-script integration, patient-matcher service, and existing VNet + Key Vault + SQL + App Insights pattern. Findings folded into §3, §4, §7, §8 of the plan.

Salesforce side: aligns with storefront-data-audit.md (B2B storefront serving at https://advancedrx.net/vforcesite/ on the CCP Login license profile), integrations/sfmc.md (the existing refill-reminder send pipeline at JBSystemFlow_Refill_Reminder_c), and the in-progress SFMC credential rotation per MARISSA-HANDOFF.md §2 #2.

Spike A: CCP Login + JWT Bearer + singleaccess verified end-to-end in ClaudeTest 2026-05-18 — full handshake (token endpoint → singleaccess → frontdoor redirect → sid cookie set) confirmed for the CCP Login license.

Spike B: Production SFMC self-send 2026-05-21 delivered both fragment + path URLs to Gmail unmodified. Caveat: Test Sends often skip click-tracking link wrapping; real journey-send wrapping behavior verifies during Phase 6a A/B rollout. AdvancedRx SFMC infrastructure facts (sending stack 11, From domain hello@advancedrx.email, vanity click-tracking host click.advancedrx.email, dual-DKIM signed, DMARC pass) captured in plan.md §4.5.