Logo jitendra.dev
Published on

Multi-Domain SaaS Authentication: A Next.js BFF Shift

Authors

Table of contents:

Multi-Domain SaaS Authentication: A Next.js BFF 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:

  1. Laravel (Sanctum) generates a token.
  2. Frontend stores it in LocalStorage.
  3. 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.com to set cookies for agency.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.

  1. Authentication architecture is dictated by domain architecture.
  2. White-label SaaS changes fundamental assumptions.
  3. Browser security rules matter more than framework features.
  4. Next.js App Router naturally supports BFF patterns.
  5. 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