Token-Based Preview Authentication for Headless CMS

Token-based preview authentication lets editors inspect unpublished content without exposing draft endpoints or weakening production access controls. It binds a cryptographically signed, short-lived credential to each preview request. Unlike session cookies or IP allowlists, stateless tokens scale across edge networks and serverless runtimes, so you keep a zero-trust boundary while editors validate layout, typography, and interactive components in real time. It’s the backbone of the broader Preview & Draft Workflow Patterns.

Token Format & Lifecycle

The pattern rests on a standard token format, usually a JWT (RFC 7519). Each preview credential carries exp (expiry), iss (issuing CMS), aud (target frontend domain), and a custom preview_mode boolean or scope array. The frontend verifies the signature against a shared secret or public key, then switches the data-fetching layer to draft mode before rendering. For algorithm selection, claim validation, and secret rotation, see Securing headless CMS preview endpoints with JWT tokens.

Give tokens a 15–30 minute TTL. Transmit them via query parameter on the CMS redirect, then immediately exchange for an httpOnly, Secure, SameSite=Lax cookie to close XSS and CSRF vectors. Never store preview credentials in localStorage or sessionStorage — they’re readable by client-side script. Revoke the token when an editor logs out or the draft is published, so orphaned sessions can’t be replayed.

Implementation

The token’s lifecycle runs from CMS mint through edge validation to a scoped cookie and explicit teardown:

sequenceDiagram
  participant Editor
  participant CMS
  participant Edge as Validation route
  participant API as CMS draft API
  Editor->>CMS: "Click Preview"
  CMS->>Edge: "Redirect with signed token (query param)"
  Edge->>Edge: "Verify signature + claims"
  alt valid
    Edge->>Editor: "Set httpOnly preview cookie, redirect to draft"
    Editor->>API: "Fetch draft (Bearer / cookie)"
    API->>Editor: "Draft content, Cache-Control private no-store"
  else invalid or expired
    Edge->>Editor: "401 / re-auth via CMS"
  end
  Editor->>Edge: "/api/exit-preview clears cookie"

1. CMS Configuration & URL Generation

Configure the CMS to build a preview URL that routes through a validation endpoint, not directly to a page template — that intermediary route is the gatekeeper. On “Preview,” the CMS signs a payload with the target slug, locale, and draft revision ID, encodes it into a token, and appends it to the validation URL. With ISR, pair this with Webhook Triggered Rebuilds so the preview reflects the latest content without a full deploy.

2. Token Validation Middleware

Intercept the request at the edge or in an API route. Validate the signature, extract claims, and establish a session. A Next.js App Router implementation using jose:

TypeScript
// app/api/preview/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';

export async function GET(request: NextRequest) {
  const token = request.nextUrl.searchParams.get('token');
  if (!token) {
    return NextResponse.json({ error: 'Missing authentication token' }, { status: 400 });
  }

  try {
    const { payload } = await jwtVerify(
      token,
      new TextEncoder().encode(process.env.PREVIEW_SHARED_SECRET)
    );

    if (payload.preview_mode !== true || !payload.target_slug) {
      throw new Error('Invalid token claims');
    }

    // Set secure httpOnly cookie for subsequent draft requests
    const response = NextResponse.redirect(new URL(`/preview/${payload.target_slug}`, request.url));
    response.cookies.set('preview_session', token, {
      httpOnly: true,
      secure: process.env.NODE_ENV === 'production',
      sameSite: 'lax',
      maxAge: 1800, // 30 minutes
      path: '/',
    });

    return response;
  } catch (error) {
    return NextResponse.json({ error: 'Invalid or expired token' }, { status: 401 });
  }
}

3. Draft Data Fetching & Session Handoff

Once the cookie is set, every fetch must inspect the session before querying the CMS. Frameworks ship built-in draft-mode toggles; custom setups inject the header explicitly. Pass the validated token as Authorization: Bearer (or read it from the cookie) to request draft content from the CMS GraphQL/REST API. The data layer must respect the preview_mode claim and never cache draft responses in shared CDNs — set Cache-Control: private, no-store on all preview routes to stop leakage to public caches.

4. Session Termination

Terminate preview sessions explicitly so stale drafts don’t persist. Expose /api/exit-preview to clear the cookie and redirect to the live URL, and check token expiry on every route transition. If a token expires mid-session, prompt re-authentication via the CMS rather than throwing — that keeps editorial continuity without exposing the API to unauthenticated fallback requests.

Production Hardening

A few things to get right at scale. Enforce strict CORS on the validation endpoints to reject unauthorized origins. Rate-limit to block token brute-forcing and replay. And make real-time channels inherit the same validation: when you pair this with Live Editing Integration Patterns, WebSocket and SSE connections must run the same token check or you’ve opened a draft-stream injection path.

Audit accessibility too: preview indicators (banners, watermarks, toolbars) need to meet WCAG contrast and keyboard-navigation standards so teams can validate inclusive design before publishing. The Next.js draft mode docs are a solid baseline for cookie handling and route interception to adapt into custom middleware.

Summary

Token-based preview moves the security model from perimeter-based to request-scoped and zero-trust. Cryptographically signed credentials, middleware validation, secure cookie handling, and strict cache isolation let you expose draft environments to editors without touching production infrastructure — scalable, auditable, and verifiable per request.