Table of Contents
Single sign-on (SSO) is the difference between a user typing one set of credentials and accessing every app they need, versus repeatedly logging in to each service—and then calling support when they forget a password. When those apps live on different domains, SSO suddenly feels impossible. Cookies don’t cross origins, browsers block iframes, and identity tokens become a liability instead of an asset. The Misar team has helped dozens of engineering teams master this exact challenge, turning fragmented authentication into a seamless experience without compromising security. In this post, we’ll share what we’ve learned, the architecture we recommend, and how to implement it using MisarIO’s ecosystem—so your users stay logged in across every corner of your digital ecosystem.
Why Cross-Domain SSO Feels Impossible (And How to Fix It)
Cross-domain SSO breaks two fundamental assumptions in web security: that cookies can be shared across origins, and that tokens can safely travel between untrusted contexts. When your CRM, dashboard, and mobile app live on app.example.com, api.example.com, and mobile.example.net, browsers refuse to share session cookies, CORS policies block token forwarding, and identity brokers become critical infrastructure.
The common pitfalls are easy to spot in hindsight:
- Cookie isolation: Browsers enforce SameSite=Lax by default, meaning session cookies set on app.example.com aren’t sent when a user visits api.example.com.
- Token leakage: Passing JWTs in URLs or postMessage channels risks exposure in logs, browser history, or cross-origin iframes.
- CORS misconfigurations: APIs reject Authorization headers from domains that aren’t explicitly allowed, breaking token exchange flows.
- Session synchronization: Logging out of one app must invalidate sessions across all apps, which is hard when cookies and tokens are siloed.
At Misar, we’ve seen teams waste months trying to shoehorn OAuth into every corner of their stack—only to realize the protocol wasn’t designed for this scenario. Instead, we use identity federation via a central broker that handles token translation, session synchronization, and cross-domain consent. This approach scales across hundreds of apps and domains while keeping security auditors happy.
The MisarIO Identity Broker Pattern
Our recommended architecture is built on three pillars: a central identity broker, short-lived tokens, and secure cross-domain communication. Here’s how it works in practice:
- Broker sits at a neutral origin (e.g., auth.misar.io)
- Apps register as clients with the broker, specifying allowed redirect URIs and token scopes
- User authenticates once at the broker, which issues a session cookie scoped to auth.misar.io
- Apps request tokens via postMessage or a hidden iframe, using the broker’s /token endpoint
- Tokens are short-lived (typically 5–15 minutes) and refreshed silently via /refresh
This design solves the cookie problem because the broker’s session cookie lives on its own origin, while apps remain isolated. Tokens are never exposed to the user’s browser history, and refresh tokens are stored in Secure, HttpOnly cookies on the broker’s domain—preventing JavaScript theft.
Broker Configuration Example (MisarIO)
``yaml
broker-config.yaml
clients:
- id: "crm-app"
redirect_uris: ["https://app.example.com/auth/callback"]
scopes: ["profile", "crm:read"]
token_ttl: 600 # 10 minutes
refresh_ttl: 86400 # 1 day
- id: "mobile-app"
redirect_uris: ["misar://auth/callback"]
scopes: ["profile", "api:write"]
token_ttl: 300 # 5 minutes
`
The broker handles token translation—so your CRM app receives a JWT with sub: "user123" and scope: "profile", while your mobile app gets a different token with scope: "api:write". This granularity is critical when apps need different levels of access.
Implementing Cross-Domain SSO in Three Steps
Adopting this pattern doesn’t require a rewrite of your apps. Here’s a pragmatic, step-by-step guide based on our work with customers across finance, healthcare, and SaaS.
Step 1: Deploy the MisarIO Identity Broker
Start by hosting the broker at a stable, neutral origin. MisarIO offers a managed broker service, or you can self-host it as a Docker container with a single YAML config file.
`bash
docker run -d \
-p 8080:8080 \
-v ./broker-config.yaml:/config.yaml \
misar/identity-broker:latest
`
Key requirements:
- The broker must use HTTPS with a valid certificate
- It must support CORS for your app domains
- It should expose /authorize, /token, and /userinfo endpoints
Once deployed, register each of your apps as OAuth clients in the broker’s admin UI or config file. Be strict with redirect URIs—wildcard domains are a common source of token leakage.
Step 2: Modify Your Apps to Use the Broker
Each app needs three small changes:
- Replace local auth logic with a call to the broker’s /authorize endpoint
- Intercept token exchange using postMessage or a hidden iframe
- Store tokens securely—never in localStorage or sessionStorage
Frontend (React) Example
`tsx
// App.tsx
import { useEffect } from 'react';
import { useAuth } from '@misar/auth-react';
function App() {
const { token, login, logout } = useAuth({
brokerUrl: 'https://auth.misar.io',
clientId: 'crm-app',
scopes: ['profile', 'crm:read'],
});
if (!token) {
return Sign in with Misar;
}
return (
Welcome, {token.sub}
Sign out
);
}
`
The @misar/auth-react library handles:
- Redirecting to /authorize
- Opening a child window to the broker
- Listening for postMessage tokens
- Storing tokens in memory (not localStorage)
- Refreshing tokens silently
For mobile apps, use deep links instead of redirects:
`swift
// iOS Swift
let authUrl = URL(string: "https://auth.misar.io/authorize?client_id=crm-app&redirect_uri=misar://auth/callback")!
UIApplication.shared.open(authUrl)
`
Step 3: Enable Silent Refresh and Session Sync
Short-lived tokens are secure but annoying if users get logged out mid-session. Use silent refresh to keep them signed in:
`tsx
// Silent refresh in a hidden iframe
const refreshIframe = document.createElement('iframe');
refreshIframe.src = 'https://auth.misar.io/refresh?client_id=crm-app';
refreshIframe.style.display = 'none';
document.body.appendChild(refreshIframe);
`
Configure the broker to rotate refresh tokens on each use, and invalidate stale tokens server-side.
For session synchronization, the broker emits events to your apps via WebSockets or Server-Sent Events (SSE):
`yaml
broker-config.yaml
events:
enabled: true
endpoints:
- "https://app.example.com/api/auth/session"
- "https://dashboard.example.com/api/auth/session"
`
When a user logs out from their CRM, the broker broadcasts a LOGOUT event to all registered endpoints, forcing immediate session invalidation.
Handling Edge Cases and Security Gotchas
Even with a solid architecture, real-world apps introduce edge cases. Here’s what we’ve seen break—along with fixes we’ve validated with MisarIO customers.
Iframes and CORS Nightmares
If your app embeds another service in an iframe (e.g., a payment modal), the embedded app can’t read the broker’s cookies. Solution: use postMessage to request tokens instead.
`js
// Parent app
const iframe = document.querySelector('iframe');
iframe.contentWindow.postMessage(
{ type: 'GET_TOKEN', clientId: 'crm-app' },
'https://embedded.example.com'
);
// Embedded app
window.addEventListener('message', (event) => {
if (event.data.type === 'GET_TOKEN') {
const token = getTokenFromStore(); // From memory
event.source.postMessage({ type: 'TOKEN_RESPONSE', token }, event.origin);
}
});
`
Always validate event.origin to prevent token theft.
Mobile Deep Link Conflicts
Mobile apps using custom URI schemes (e.g., myapp://auth/callback) can clash with other apps. Use Universal Links (iOS) or App Links (Android) instead:
`xml
`
This ensures the OS opens your app only when the broker redirects to a verified domain.
Token Scoping for Multi-Tenant Apps
In multi-tenant environments (e.g., a SaaS platform), tokens must include tenant context to prevent privilege escalation. Extend your JWT with a tenant_id:
`yaml
broker-config.yaml
scopes:
- name: "tenant:read"
claims:
- "tenant_id"
- name: "tenant:admin"
claims:
- "tenant_id"
- "role"
`
Your backend APIs should validate tenant_id and reject requests where it doesn’t match the user’s active context.
Browser Extensions and Privacy Modes
Privacy-focused users running extensions like uBlock or using Incognito mode can break token flows by blocking cross-origin requests. Test your implementation in these environments early. MisarIO’s broker includes fallback flows for users with strict privacy settings, such as:
- Using localStorage for token storage when cookies are blocked (with CSP headers to prevent XSS)
- Offering a "copy token" button for manual paste into apps
Scaling to Hundreds of Apps and Domains
As your ecosystem grows, manual client registration becomes unsustainable. Here’s how to automate and secure large-scale SSO:
Dynamic Client Registration
Let apps register themselves via API:
`bash
curl -X POST https://auth.misar.io/register \
-H "Authorization: Bearer system-token" \
-d '{
"client_name": "analytics-tool",
"redirect_uris": ["https://analytics.example.com/callback"],
"scopes": ["profile", "analytics:read"],
"token_ttl": 300
}'
`
Requires a system-level token with admin scope and strict rate limiting.
Domain Allowlisting with Wildcards
Allow subdomains dynamically:
`yaml
broker-config.yaml
allowed_domains:
- "*.example.com"
- "*.staging.example.net"
- "app.misar.io"
``
Validate domains at token request time to prevent open redirectors.
Automated Token Rotation
Use short-lived tokens (5–