Skip to main content

Cross-Domain Authentication

NearSync runs three applications on three subdomains: admin.nearsync.tech, portal.nearsync.tech, and nearsync.tech. Users must maintain a single authenticated session across all three without visible redirects or re-authentication prompts. This page explains how chunked cookie storage solves the cross-domain session problem.

The Problem

Supabase Auth issues JWT tokens that include custom claims for organization ID, role, and feature flags. These tokens routinely exceed 4 KB. HTTP cookies have a hard limit of 4,096 bytes per cookie. The default Supabase Auth SDK stores the access token in a single cookie, which silently fails when token size exceeds this limit.

Since the three applications live on separate subdomains, session storage must be accessible across all of them. This rules out approaches that are scoped to a single origin.

NearSync implements chunked cookie storage in the @nearsync/supabase-client package. When a JWT token exceeds 3,500 bytes, it is split into multiple cookies with sequential suffixes, all scoped to the .nearsync.tech parent domain.

How It Works

On login (writing the session):

  1. Supabase Auth issues a JWT (access token + refresh token)
  2. The storage adapter checks the token size
  3. If the token fits in a single cookie (under 3,500 bytes), it is stored as one cookie
  4. If the token exceeds 3,500 bytes, it is split into sequential chunks:
    • sb-auth-token-0 = first 3,500 bytes
    • sb-auth-token-1 = next 3,500 bytes
    • Additional chunks as needed
  5. All chunks are set with Domain=.nearsync.tech and Secure flags

On navigation (reading the session):

  1. User navigates from admin.nearsync.tech to portal.nearsync.tech
  2. The browser automatically includes all .nearsync.tech cookies in the request
  3. The storage adapter detects chunked cookies by their sequential suffixes
  4. Chunks are reassembled in order to reconstruct the full JWT
  5. The session is validated and the user continues without re-authentication

On logout (clearing the session):

  1. All chunked cookies are enumerated and deleted
  2. A cache-busting mechanism ensures stale chunks from previous sessions do not persist
PropertyValueReason
Domain.nearsync.techAccessible from all subdomains
SecuretrueHTTPS only in production
SameSiteLaxAllows cross-subdomain navigation while protecting against CSRF
Path/Accessible from all routes
Chunk size3,500 bytesStays under the 4,096-byte limit with room for cookie name, metadata, and encoding overhead

JWT Lifecycle

User logs in (any subdomain)
|
v
Supabase Auth issues JWT
- access_token (contains user_id, email, custom claims)
- refresh_token
- TTL: 3600 seconds
|
v
Storage adapter writes chunked cookies
- Domain: .nearsync.tech
- Chunks: 1-3 cookies depending on token size
|
v
User navigates between subdomains
- Browser sends cookies automatically
- Storage adapter reassembles JWT
- Session persists seamlessly
|
v
Token expires (after TTL)
|
v
Supabase SDK auto-refreshes
- Uses refresh_token to obtain new access_token
- Storage adapter rewrites chunked cookies with new token
- Process is invisible to the user

Token Size Considerations

Token SizeChunks RequiredTypical Scenario
Under 3.5 KB1Standard user with basic claims
3.5 - 7 KB2User with extended custom claims (org metadata, feature flags)
Over 7 KB3+Unusual; indicates custom claims may need pruning

As custom claims accumulate in the JWT payload (for example, adding more metadata to the token), token size grows. NearSync monitors chunk count on each auth refresh and alerts if the average exceeds 1.5 chunks, indicating that token size is trending upward.

AuthProvider Integration

The chunked cookie system is transparent to application code. The AuthProvider component (from @nearsync/supabase-client) wraps the entire application tree and handles session initialization, profile loading, and realtime subscriptions.

AuthProvider mounts
|
v
Initialize Supabase client with custom storage adapter
|
v
Storage adapter reads cookies, reassembles JWT
|
v
Fetch user profile from profiles table
|
v
Subscribe to role_permissions realtime channel
|
v
Load SystemManifest via useSystemManifest()
|
v
useAuth() hook provides:
{
session, // Supabase session object
user, // Supabase user (id, email)
profile, // Profile record (org_id, name, avatar)
manifest, // Full SystemManifest for this org
permissions, // Role-based permissions (realtime)
isModuleEnabled(), // Check module access
isFeatureEnabled(), // Check feature flag
checkPermission(), // Check RBAC permission
mfaEnabled, // TOTP enrollment status
signUp(),
signIn(),
signOut(),
}

Application components consume useAuth() without any awareness of the underlying cookie chunking mechanism.

MFA Support

NearSync supports TOTP-based multi-factor authentication via Supabase's MFA API:

  1. User requests MFA enrollment from their profile settings
  2. Supabase generates a TOTP secret and QR code
  3. User scans the QR code with an authenticator app (Google Authenticator, Authy, etc.)
  4. User enters a 6-digit verification code to confirm enrollment
  5. Future logins require both password and TOTP code
  6. MFA status is reflected in useAuth().mfaEnabled

Organizations can enforce MFA for all users through the security.mfa_required flag in the SystemManifest.

Alternatives Considered

Several cross-domain session strategies were evaluated before choosing chunked cookies:

Shared Redis session store - Standard pattern but adds infrastructure (Redis deployment on a serverless platform requires a managed service). Introduces session replication complexity and requires a custom auth server to manage the session lifecycle.

OAuth2 redirect-based SSO - Industry standard with strong separation of concerns, but introduces visible redirects between subdomains (poor UX), extra round-trip latency, and the complexity of implementing a custom OAuth provider.

Single domain with path-based routing (nearsync.tech/admin, nearsync.tech/portal) - Trivial session sharing since everything is on the same domain, but requires a proxy layer and constrains the deployment architecture. Each app needs its own Vercel project, which maps naturally to separate subdomains.

localStorage with postMessage - No server involvement, but fragile. postMessage is blocked in incognito mode, requires iframes on both sides, and has race conditions with multiple tabs.

Chunked cookies were selected because they require no additional infrastructure, work with the standard Supabase Auth SDK, and handle cross-subdomain sessions transparently.

Security Considerations

Non-HttpOnly cookies: The Supabase SDK requires client-side access to the token for API requests, which means the cookies cannot be marked HttpOnly. XSS attacks could theoretically steal tokens. This is mitigated by:

  • Content Security Policy (CSP) headers that restrict script sources
  • HTTPS enforcement on all production domains
  • SameSite=Lax to prevent CSRF-based cookie theft

Token refresh security: Refresh tokens are rotated on each use. A stolen refresh token becomes invalid after the legitimate client refreshes, limiting the window of exploitation.

Cookie cleanup: On logout, all chunked cookies are explicitly deleted. A cleanup routine also runs on session initialization to remove orphaned chunks from previous sessions that may not have been properly cleared.

Future Migration Path

The chunked cookie approach is designed as a pragmatic solution for the current architecture. As NearSync evolves toward an API Gateway pattern, session management will migrate to an opaque session token model where the gateway handles JWT validation server-side and issues a lightweight, single-cookie session token to the browser. This will eliminate the need for chunking entirely.