- Published on
Multi-Domain SaaS Authentication: A Next.js BFF Shift
- Authors
-
-
- Name
- Jitendra M
- @_JitendraM
-
Table of contents:
-
Authentication Architecture Shift in a White-Label Multi-Domain SaaS: From LocalStorage to a Next.js BFF
-
Context: What Palactix Actually Is
-
Phase 1: LocalStorage + Bearer Token
-
The Real Constraint: Root Domain Boundaries
-
SSR Reality
-
The Shift: Using the Server We Already Had
- Implementation Overview
-
Why This Aligns with Palactix
-
Tradeoffs
-
Lessons from This Shift
Authentication Architecture Shift in a White-Label Multi-Domain SaaS: From LocalStorage to a Next.js BFF
This is part of the Palactix Engineering Notes — a public record of architectural decisions as the system evolves.
This note documents why authentication moved from a simple LocalStorage + Bearer model to a Next.js BFF (Backend for Frontend) boundary in a white-label multi-domain SaaS environment.
Designing authentication for a white-label multi-domain SaaS introduces constraints that do not exist in traditional single-domain applications.
This was not a security panic. It was a topology decision.
Context: What Palactix Actually Is
Palactix is not a single-domain SPA.
It supports:
-
api.palactix.com(Laravel API) -
app.palactix.com(Next.js frontend) -
agency1.palactix.com(workspace subdomains) - Custom CNAME domains (e.g.
social.agency.com) - Multi-tenant isolation
The backend and frontend are separate origins. White-label domains are first-class citizens.
Authentication must work across:
- Multiple subdomains
- Different root domains
- Server-side rendering
- Tenant isolation boundaries
That environment changes the rules.
Phase 1: LocalStorage + Bearer Token
Initial implementation was straightforward:
- Laravel (Sanctum) generates a token.
- Frontend stores it in LocalStorage.
- Each request sends:
Authorization: Bearer <token>
For a single-origin SPA, this is clean and practical.
Why it worked initially:
- Stateless
- Easy debugging
- No cookie configuration
- Clear request model
But architecture pressure surfaced once white-label entered the system.
The Real Constraint: Root Domain Boundaries
The turning point was this:
Cookies cannot be shared across different root domains.
A browser will never allow:
-
palactix.comto set cookies foragency.com
This is not a framework issue. This is a browser rule.
In a white-label SaaS where agencies map:
-
social.agency.com
You do not control the root domain.
That single constraint makes traditional cookie-based SPA auth fragile.
LocalStorage avoids cookie scoping issues — but introduces fragmentation:
- Each domain manages its own session lifecycle.
- Logout and expiration coordination become distributed.
- Server-side rendering cannot see LocalStorage.
The issue wasn’t token storage. The issue was boundary ownership.
SSR Reality
Palactix uses Next.js App Router.
LocalStorage is invisible during server-side rendering.
That means:
- The server cannot read session state.
- First render cannot include authenticated data.
- You depend on client hydration to know who the user is.
For a white-label SaaS aiming for consistent first-load behavior, that becomes a structural limitation.
The Shift: Using the Server We Already Had
The solution was not “add another service.”
Next.js already provides a server boundary via Route Handlers.
Instead of:
Browser → Laravel API
The flow became:
Browser → Next.js Server → Laravel API
The Next.js layer acts as a BFF.
Important distinction:
This was not additional infrastructure. It was using the existing layer correctly.
Implementation Overview
Laravel (Token Issuance)
$token = $user->createToken('auth-token')->plainTextToken;
return response()->json([
'access_token' => $token,
]);
Laravel remains responsible for authentication logic.
Next.js BFF Layer
On login:
export async function POST(request: NextRequest) {
const apiResponse = await fetch(`${API_URL}/login`, {
method: "POST",
body: await request.text(),
headers: { "Content-Type": "application/json" }
});
const data = await apiResponse.json();
const response = NextResponse.json({ success: true });
response.cookies.set({
name: "palactix_access_token",
value: data.access_token,
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/",
});
return response;
}
Key properties:
- Token never exposed to browser JavaScript
- Cookie scoped to the current domain
- Server controls forwarding
Forwarding Authenticated Requests
const token = cookies().get("palactix_access_token")?.value;
await fetch(`${API_URL}/me`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
The browser does not manage tokens. The server does.
This centralizes session behavior across domains.
Why This Aligns with Palactix
In this topology:
- Root domains may differ
- White-label is mandatory
- API and frontend are separate
- SSR is used
BFF + HttpOnly cookies create a clear trust boundary:
- Browser stays dumb
- Server handles session
- Laravel remains token authority
This is not universally superior. It is topology-aligned.
Tradeoffs
What this introduces:
- Additional server hop
- More layered debugging
- Slightly higher complexity
What it simplifies:
- Multi-domain session consistency
- Token exposure surface
- SSR authentication visibility
Infrastructure decisions are always tradeoffs.
Lessons from This Shift
One key realization: multi-domain authentication must be designed around browser root-domain constraints, not just framework capabilities.
- Authentication architecture is dictated by domain architecture.
- White-label SaaS changes fundamental assumptions.
- Browser security rules matter more than framework features.
- Next.js App Router naturally supports BFF patterns.
- Auth decisions should be made early — refactoring later is expensive.
This is not the final state of Palactix authentication. It is the current structural alignment.
More notes will follow as the system evolves.
—
Palactix Engineering Notes