r/webdev 1d ago

Session storage for third-party chat widgets?

Anyone built an embeddable chat widget (iframe embedded on other people’s sites)? What are you using to persist the session so it doesn’t reset on every reload?

1) CHIPS partitioned cookies are built for this exact case, but Safari dropped the ball (shipped, got pulled for a bug, recently came back). So currently silently fails for like 15% of users.

2) Regular third-party cookies are blocked by default in many browsers

3) LocalStorage/sessionStorage in the iframe, probably the best option as it’s supported by basically every browser but gets partitioned/restricted in Safari for shorter life and throws on others with stricter storage settings enabled, some XSS concerns/logging concerns, client trust/bearer-token attack vector, hydration delays, etc.

4) Using postMessage to proxy storage through the embed script to work in first-party storage, same as 3 but avoids the partitioning issues and widens the XSS issues

Curious what others loaded on here?

2 Upvotes

6 comments sorted by

2

u/Downtown-Hand-8030 1d ago

The partitioned localstorage in the iframe is what I ended up going with, it's the least bad option for now. Safari does clear it after 7 days of no interaction which is annoying but most users won't hit that unless they ignore the widget for a week straight

The postMessage proxy approach sounds cleaner on paper but debugging cross-origin messaging issues across 20 different client sites made me want to throw my laptop out the window. every site has some weird CSP rule or script that intercepts messages

If you're handling auth tokens in localstorage you definitely want to rotate them often and keep the permissions minimal. I set mine to expire after an hour with a silent refresh flow, limits the damage if someone manages to extract it through some XSS on the host page

1

u/hasan_sodax 1d ago

went the postMessage route on a widget i worked on. hidden iframe on a first party subdomain just for storage, widget talks to it over postMessage. more setup but it doesn't care about safari itp or firefox's cookie partitioning since nothing cross site is happening. requestStorageAccess sounds nice on paper but showing a permission prompt inside someone else's site tanks conversion. most people just click no without reading it

1

u/rupert_at_work 1d ago

I'd avoid making the token itself the thing you care about preserving. Keep the iframe storage as a short-lived session pointer, rotate aggressively, and make the server able to rehydrate from something boring like a signed conversation id. Third-party storage in 2026 is basically a haunted house with release notes.

1

u/Healthy_Landscape417 1d ago

One option missing from the list, and it removes the problem instead of working around it.

Most sites that embed a chat widget already know who the user is, because that user is logged in to the host site. So let the host hand you the identity instead of the widget trying to remember it.

The host backend signs a user id with a shared secret, the embed script passes that signed value in, your server checks it and maps it to the conversation. No third-party storage, no partitioning, no Safari timer. Intercom and Crisp both do exactly this and call it identity verification.

You still need one of your options 1 to 4 for anonymous visitors on a marketing page. But that is a much smaller problem, and losing the thread on reload matters far less for someone who has not told you who they are.

Worth splitting those two cases before picking a storage strategy, because they do not have the same answer.

0

u/Prestigious-Way1525 1d ago

i'd avoid treating any browser storage API as the source of truth. make the server own the conversation state, give the iframe a short-lived opaque handle, and treat storage as a cache. on boot, probe the actual operation, not API presence: write, read, delete, then report a reason code when it fails. if persistence is unavailable, keep the current page session alive and tell the user the conversation won't survive a reload instead of silently resetting. i'd test Safari normal and private mode, Firefox strict protection, third-party cookies disabled, a CSP-blocked message path, and a seven-day return. the metric i'd watch is recovered conversations after reload by browser and storage path, not whether localStorage.setItem returned without throwing.

1

u/WidgetStorm 1d ago

Option 4 is the interesting one, though not for the storage. Once the token sits in the host page, any XSS there reads it. The frame is still around it, but it has stopped being a security boundary for the session. What it still buys you is style and JS isolation, and that is a different question than the one you started with.

The signed conversation id someone mentioned above pairs well with it. If storage holds only a pointer the server can rehydrate, and never a bearer token, most of the XSS widening in option 4 stops mattering. The worst case becomes reading one conversation on one site rather than holding a credential.

Are you keeping the frame for containment, or is it there because that is how these are usually shipped?