Next.js + Vercel
Next.js on Vercel is an excellent match for Sesamy's BFF (cookie-based) auth pattern. Vercel automatically runs next.config.ts rewrites at its Edge Network, so the proxy hops that handle /auth/* and /api/* are resolved at a PoP close to the user — not back in a Node.js origin server — keeping latency low.
Why BFF?
The SPA (Auth0) flow stores tokens in localStorage, which is accessible to any JavaScript on the page. BFF keeps tokens server-side in HttpOnly cookies that JS can never read, eliminating the XSS token-theft surface entirely.
How it works
Browser Vercel Edge Sesamy API
| | |
|-- GET /auth/* --> |-- rewrite -------> |
|<- Set-Cookie ---- |<------------------ |
| | |
|-- GET /api/* ---> |-- rewrite -------> |
|<- data ---------- |<------------------ |All of /auth/* and /api/* are transparently proxied at the Edge. Cookies are set on your own domain, so they are first-party — no custom domain or CNAME required.
Prerequisites
- Next.js 14 or later (App Router or Pages Router both work)
- A Vercel project (or a domain that resolves to your Next.js deployment)
- BFF / Token Handler enabled for your vendor — contact Sesamy to activate this
1. Proxy auth and API routes
Add rewrites to next.config.ts so Vercel forwards both the Sesamy auth flow and the data API through your domain:
// next.config.ts
import type { NextConfig } from 'next';
const SESAMY_API = process.env.SESAMY_API_URL ?? 'https://api.sesamy.com';
const nextConfig: NextConfig = {
async rewrites() {
return [
// BFF auth — login, callback, logout, session check
{
source: '/auth/:path*',
destination: `${SESAMY_API}/auth/:path*`,
},
// Sesamy data API — entitlements, contracts, products, …
{
source: '/api/:path*',
destination: `${SESAMY_API}/:path*`,
},
];
},
};
export default nextConfig;Vercel runs rewrites at its Edge Network by default — the request never reaches your Node.js origin, so there is no cold-start overhead on these proxy hops.
Conflicts with your own API routes
If your app also has Next.js Route Handlers under /api/*, the rewrite above will shadow them. Either scope the Sesamy rewrite to a sub-path (e.g. /api/sesamy/:path* → ${SESAMY_API}/:path*) and set api.endpoint to /api/sesamy in the sesamy-js config, or list your own routes first using the beforeFiles array:
async rewrites() {
return {
beforeFiles: [
// your own API routes go here — they take precedence
],
afterFiles: [
{ source: '/auth/:path*', destination: `${SESAMY_API}/auth/:path*` },
{ source: '/api/:path*', destination: `${SESAMY_API}/:path*` },
],
};
},2. Configure sesamy-js
Point sesamy-js at the local proxy endpoints and enable cookie auth:
{
clientId: process.env.NEXT_PUBLIC_SESAMY_CLIENT_ID,
api: {
endpoint: '/api', // proxied to api.sesamy.com by Vercel Edge
},
auth: {
useHttpCookies: true, // activates BFF cookie plugin; no authPlugin needed
},
}The /auth/* path is used implicitly by sesamy-js for the login/logout/callback/userinfo flows — it always calls /auth/* relative to the current origin, regardless of api.endpoint.
3. Load sesamy-js in App Router
Option A — Scripts Host bundle (simplest)
If you use the Sesamy Scripts Host, the bundle already contains your vendor config. Just load the script:
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Script
src="https://scripts.sesamy.com/s/YOUR_VENDOR_ID/bundle/stable.js"
strategy="afterInteractive"
/>
</body>
</html>
);
}Contact Sesamy to have your vendor's Scripts Host config updated to use api.endpoint: '/api' and auth.useHttpCookies: true.
Option B — npm package with inline config
Install the package:
pnpm add @sesamy/sesamy-jsnpm install @sesamy/sesamy-jsThen inject the config as a JSON element (sesamy-js picks it up on load) and load the script:
// app/layout.tsx
import Script from 'next/script';
export default function RootLayout({ children }: { children: React.ReactNode }) {
const sesamyConfig = {
clientId: process.env.NEXT_PUBLIC_SESAMY_CLIENT_ID,
api: { endpoint: '/api' },
auth: { useHttpCookies: true },
};
return (
<html lang="en">
<head>
<script
type="application/json"
id="sesamy-js"
dangerouslySetInnerHTML={{ __html: JSON.stringify(sesamyConfig) }}
/>
</head>
<body>
{children}
<Script
src="https://cdn.sesamy.com/sesamy-js/stable/sesamy-js.iife.js"
strategy="afterInteractive"
/>
</body>
</html>
);
}Option C — Programmatic init
For full control, call init() from a Client Component:
// components/SesamyProvider.tsx
'use client';
import { useEffect } from 'react';
import { init } from '@sesamy/sesamy-js';
export function SesamyProvider() {
useEffect(() => {
init({
clientId: process.env.NEXT_PUBLIC_SESAMY_CLIENT_ID!,
api: { endpoint: '/api' },
auth: { useHttpCookies: true },
});
}, []);
return null;
}4. Auth buttons (Client Component)
// components/AuthButton.tsx
'use client';
import { useEffect, useState } from 'react';
export function AuthButton() {
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
useEffect(() => {
const onReady = async () => {
setIsAuthenticated(await window.sesamy.auth.isAuthenticated());
};
window.addEventListener('sesamyJsReady', onReady);
window.addEventListener('sesamyJsAuthenticated', () => setIsAuthenticated(true));
window.addEventListener('sesamyJsLoggedOut', () => setIsAuthenticated(false));
return () => {
window.removeEventListener('sesamyJsReady', onReady);
};
}, []);
if (isAuthenticated === null) return null;
return isAuthenticated ? (
<button onClick={() => window.sesamy.auth.logout()}>Log out</button>
) : (
<button onClick={() => window.sesamy.auth.login()}>Log in</button>
);
}5. Gate content in Server Components
Use the Sesamy SDK server-side to check entitlements. Forward the session cookie from the incoming request so the SDK can authenticate on behalf of the user:
// app/article/[id]/page.tsx
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { client } from '@sesamy/sdk';
export default async function ArticlePage({ params }: { params: { id: string } }) {
const cookieStore = await cookies();
const sessionCookie = cookieStore.getAll()
.map((c) => `${c.name}=${c.value}`)
.join('; ');
const sdk = client({ baseUrl: 'https://api.sesamy.com' });
const { data, error } = await sdk.entitlements.list(
{},
{ headers: { cookie: sessionCookie } },
);
if (error || !data?.some((e) => e.itemId === params.id)) {
redirect(`/?paywall=${params.id}`);
}
return <article>{/* premium content */}</article>;
}6. Vercel environment variables
Add these in Vercel → Project → Settings → Environment Variables:
| Variable | Example | Exposed to browser |
|---|---|---|
NEXT_PUBLIC_SESAMY_CLIENT_ID | my-vendor | ✅ yes |
SESAMY_API_URL | https://api.sesamy.com | ❌ no |
SESAMY_API_KEY | sk_… | ❌ no |
SESAMY_API_URL lets you point at a different region or a local Worker during development without changing code:
# .env.local — used by `next dev`
NEXT_PUBLIC_SESAMY_CLIENT_ID=your-client-id
SESAMY_API_URL=https://api.sesamy.com # or http://localhost:8787 for local Worker
SESAMY_API_KEY=your-management-api-key # only needed for server-side entitlement checksFurther reading
- BFF Authentication — full protocol reference
- Scripts Host — automatic bundle delivery with vendor config baked in
- Sesamy SDK — Node.js/Edge SDK for server-side entitlement checks
- Vercel rewrites docs — how Vercel Edge rewrites work