Siddhant DevalAuthor
Senior Full-Stack Engineer·Oct 6, 2026·14 min read
Cross-App State, Communication, and Authentication
Share as little state as possible across micro-frontend boundaries — and when you must share it, share it via the platform (URL, events, cookies) not via JavaScript objects in window. This article covers the three communication layers, their coupling costs, and the auth patterns every MFE system gets wrong.
Technical Series
Micro-Frontend Architecture
Part 6 of 9
Cross-App State, Communication, and Authentication
A micro-frontend is not a smaller app — it is a domain boundary enforced at the deployment layer. If you can't explain the business capability it owns, you haven't drawn the boundary yet.
The most seductive mistake in micro-frontend architecture is the global state object. You have five independently deployed apps that all need to know whether the user is logged in. Someone puts
window.__auth = { user, token } in the shell's bootstrap script. It works. It works for six months. Then the Catalog remote starts storing its own data on window.__catalog. The Checkout remote reads from window.__auth in a useEffect. A memory leak appears when remotes unmount without cleaning up their listeners. Two years later, window is a shared mutable state object that no team fully understands and everyone is afraid to change.The rule for this article: share as little state as possible, and when you must share it, share it via the platform — not via JavaScript objects that cross the boundary invisibly.
1. The Isolation Principle
1.1 Why window Is the Wrong Boundary
window is globally accessible to every JavaScript module running in the browser tab. When you store cross-app state on window, you create:Invisible coupling — Any remote can read or write any key on
window. There is no TypeScript type, no contract, and no enforcement preventing the Checkout remote from accidentally overwriting the Catalog remote's cached product list.Memory leaks — Remote applications mount and unmount as users navigate. If a remote stores event listeners or data on
window and does not clean up on unmount, the data persists indefinitely. In a single-page application where remotes mount and unmount frequently, this accumulates.Race conditions — When multiple remotes write to the same
window key concurrently, the last writer wins. There is no transaction, no locking, and no notification to other readers that the value changed.typescript
The fix is not "use a better key naming convention." The fix is eliminating
window as a communication channel entirely.2. The Three Communication Layers
Every legitimate cross-app communication need maps to exactly one of three mechanisms, ordered by coupling cost:

Expand
2.1 Layer 1: URL and Query Parameters
The URL is the most durable state container in the browser. It survives page reloads, can be shared as a link, is captured in analytics, and is readable by any remote without any coordination:
typescript
URL state is the correct mechanism for:
- Navigation and filter state the user might bookmark or share
- Cross-remote coordination that is driven by navigation events
- Any state that must survive a page refresh
2.2 Layer 2: Custom DOM Events
When two remotes need to communicate without the user navigating, and the communication does not need to survive a page reload, Custom DOM Events are the correct mechanism:
typescript
typescript
typescript
Crucial Requirement
Event schema governance is critical. The event type strings (
'cart:updated', 'auth:signout') are the contract between teams. Publish the schema as a versioned NPM package or a shared remote (@example/mfe-events). Never hardcode event type strings as magic strings in individual remotes — they become unrefactorable.2.3 Layer 3: Shared State Remote
When ≥3 remotes need the same piece of state and URL/events are insufficient, a shared state remote is justified:
typescript
javascript
Performance / Safety Warning
A shared state remote creates a deployment coupling: all consuming remotes must be compatible with the store's interface. Adding a new field is safe; removing or renaming one is a breaking change. Treat the store's exported interface as a versioned API and introduce changes through deprecation cycles, not renames.
3. Authentication Across Origins
3.1 Why Auth Is Different
Authentication state is the most widely shared state in any MFE system — every remote needs to know who the user is. It is also the state with the highest security consequences if handled incorrectly.
The naive pattern — storing the JWT access token in
localStorage or on window — exposes it to every script running in the browser tab. In a micro-frontend system where multiple independently deployed origins load JavaScript, this means any of those origins (including compromised third-party dependencies) can read the token.3.2 The httpOnly Cookie Strategy
The correct mechanism for sharing authentication across MFE origins is an httpOnly cookie scoped to the shared domain:
With this setup, the browser automatically sends the
session_token cookie with every API request to any subdomain of example.com. No JavaScript can read the token value — it is opaque to all running scripts. The auth server validates the cookie on each request.Performance / Safety Warning
SameSite: Strict blocks the cookie on cross-origin navigations (e.g., a user clicking an external link back to your site). SameSite: Lax is the correct value for most authentication cookies — it blocks cross-site POST requests (CSRF protection) while allowing same-site navigation. SameSite: None requires Secure: true and is only appropriate when cross-site cookie sending is explicitly required (e.g., embedded iframes from different origins).3.3 OAuth/PKCE Callback Ownership
When using OAuth with the PKCE flow, the authorization server redirects back to your application with a
?code= query parameter. In a micro-frontend system, exactly one application must handle this callback:
Expand
typescript
3.4 Session Invalidation Propagation
When the user signs out — from any remote — all remotes must respond:
typescript
Summary
| Concept | Rule |
|---|---|
window as shared state | Never — invisible coupling, memory leaks, race conditions |
| URL parameters | First choice — survives reload, zero coupling, shareable |
| Custom DOM Events | Second choice — composed: true for Shadow DOM; typed schema in shared package |
| Shared state remote | Third choice — only when ≥3 remotes and lower layers are insufficient |
| Auth tokens | httpOnly cookie on shared eTLD+1 domain — never localStorage or window |
| OAuth callback | Owned by exactly one remote (the Shell) — multiple registrations cause race conditions |
What's Next
In Part 7, we redesign the testing pyramid for distributed frontend systems. Unit tests stay local; contract tests guard the remote boundary using Pact; E2E tests cover the 3–5 most critical user journeys only. We also cover the type contract approach — using@module-federation/dts-pluginas a build-time integration test gate. Part 7 → Testing Strategy: Contracts, Integration Boundaries, and E2E Scope
References
Research & Synthesis Note
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.
#Micro-Frontends#State Management#Authentication#OAuth#Custom Events#Cross-Origin
Technical Series
Micro-Frontend Architecture
Part 6 of 9