offers.example.com for marketing and app.example.com for the logged-in experience — you need a way to carry the customer’s identity across the hop without forcing them to log in again and without losing UTM parameters, affiliate IDs, or cart state.
ElasticFunnels ships a stateless, encrypted session-handoff primitive you can use from backend scripts. The source domain mints a short-lived token; the destination domain verifies it and establishes the session.
When to use it
- Redirecting from a marketing site to a logged-in app on a different subdomain.
- Returning a visitor to a different brand-owned domain after an external redirect (payment gateway, SSO, etc.).
- Bridging a custom login flow that runs on one domain but needs to deposit the session on another.
- Both domains share the same parent and you can use a shared-domain cookie.
- You already have the customer’s credentials and can log them in on the destination via
session.setCustomer(). - The hop is to an entirely unrelated brand — tokens are brand-scoped and will not decrypt under a different brand’s configuration.
How it works
1. Mint a token (source domain)
In a backend script on the source page:- Default TTL: 60 seconds. Override with
session.getToken(ttlSeconds). Max600. - Single-use. Replaying a consumed token is rejected.
v1.<base64url>format — opaque, URL-safe.
2. Consume the token (destination domain)
Nothing to do. The EF runtime mounts a global middleware that inspects every request for?ef_session=<token> (alias: ?setSessionToken=<token>). On a valid token it:
- Decrypts and verifies the envelope using the destination brand’s key.
- Regenerates
session.id(prevents session fixation). - Preserves non-identity session keys —
utm_source,utm_medium,utm_campaign,utm_content,utm_term,attribution,affiliate_id,aff_id,subid,click_id,visitor_id,first_visit_at,cart,locale,theme_preview,flash. - Writes the customer identity via
setCustomer(). - Saves the session and 303-redirects to the same URL with the token param stripped so it never lands in browser history or server logs.
APP_KEY) the middleware silently falls through — the request continues unauthenticated. Nothing 500s; your marketing pages still render for anonymous visitors.
3. Consume manually (optional)
If the token arrives somewhere other than the query string — for example a form submission, custom header, or webhook body — consume it yourself:setFromToken returns false on every failure mode; never throws.
Security model
Brand isolation
Each brand’s encryption key is derived at runtime:APP_KEY still end up with disjoint keys because the HKDF salt is the brand id. A token minted for brand-a cannot be decrypted by brand-b’s runtime: AEAD authentication fails before any plaintext is exposed. The brand field in the payload is re-checked after decryption as a second gate.
There is no per-brand
handoff_app_key column, no admin UI, and no rotation dance. Rotating APP_KEY is a platform-wide operation; per-brand separation comes from the HKDF salt.Envelope
- AEAD: AES-256-GCM
- Nonce: 12 random bytes per token
- Tag: 16 bytes, verified before payload JSON is parsed
Replay protection
Every token carries a UUIDv4jti. The receiver records it in an in-memory LRU (capped at 10,000 entries) for the duration of the token’s TTL. A second attempt to consume the same token is rejected.
Session fixation
On every successful consume, the runtime callssession.regenerate() before writing the customer. The old session id is invalidated, so a pre-planted session cookie cannot be hijacked by presenting a handoff URL to the victim.
Environment
Session handoff requiresAPP_KEY to be set — the same 32-byte base64 value used for Laravel-compatible encryption. No other environment variables are needed.
APP_KEY is empty or not base64-encoded, session.getToken() returns null and the middleware silently skips every token.
Full example — cross-domain login bridge
Source (offers.example.com/app-bridge) — customer has just verified their identity via a 4-digit code, session is authenticated, now send them to the app:
app.example.com/dashboard) — no code needed. The global handoff middleware consumes ?ef_session= before your page renders. Your page sees a fresh session with is_customer: true, the visitor’s email, and the preserved UTM/attribution keys from their original landing.
Related
session.setCustomer()— establish a customer session from a custom login flow without cross-domain handoff.- Backend script actions — full reference for
session.*,cookie.*,redirect(),setVariable().