@typeb-digital/nucleus-nextjs
v0.0.1
Published
Next.js (App Router) auth SDK for the Nucleus data platform — BFF pattern, encrypted httpOnly cookie sessions, great DX
Readme
@typeb-digital/nucleus-nextjs
Next.js App Router auth for the Nucleus platform — with the DX Next.js auth usually lacks.
Drop in middleware, wrap a provider, link to /auth/login, read getSession() in a Server Component. Done. No callback wiring, no token plumbing, no giant config object.
It uses a backend-for-frontend (BFF) model: the server owns the PKCE exchange and token refresh, tokens live in an encrypted, httpOnly cookie, and client code gets a thin useUser() hook. The browser never holds the refresh token — honoring Nucleus's "token never in the browser" rule.
Auth only. Like
@typeb-digital/nucleus-client, this package never holds the app token and never calls data endpoints. For data, use@typeb-digital/nucleus-sdkfrom your Server Components / Route Handlers.
Installation
npm install @typeb-digital/nucleus-nextjs
# peer deps: next >= 14.2, react >= 19Environment
NUCLEUS_APP_ID=app_xxxxxxxxxxxx # public app id from the Nucleus dashboard
APP_BASE_URL=http://localhost:3000 # this app's URL — builds the redirect URI
NUCLEUS_SESSION_SECRET=<32-byte hex> # cookie encryption — `openssl rand -hex 32`
# NUCLEUS_BASE_URL=https://nucleus.typeb-lab.online # optional, this is the defaultRegister <APP_BASE_URL>/auth/callback as a redirect URI on your app in the Nucleus dashboard.
Quick start (4 steps)
1. Create the client — lib/nucleus.ts:
import { createNucleusClient } from '@typeb-digital/nucleus-nextjs';
export const nucleus = createNucleusClient({
protectedRoutes: ['/dashboard'], // optional: middleware guards these
});2. Mount the middleware — middleware.ts:
import { nucleus } from '@/lib/nucleus';
export const middleware = nucleus.middleware;
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };3. Wrap your layout — app/layout.tsx:
import { NucleusProvider } from '@typeb-digital/nucleus-nextjs/client';
import { nucleus } from '@/lib/nucleus';
export default async function RootLayout({ children }) {
const session = await nucleus.getSession();
return (
<html>
<body>
<NucleusProvider initialUser={session?.user ?? null}>{children}</NucleusProvider>
</body>
</html>
);
}4. Use it. Login/logout are plain links — no JS:
<a href="/auth/login">Sign in</a>
<a href="/auth/logout">Sign out</a>// client component
'use client';
import { useUser } from '@typeb-digital/nucleus-nextjs/client';
const { user, isLoading } = useUser();// Server Component / Route Handler / Server Action
const session = await nucleus.getSession(); // { user, accessToken, expiresAt } | null
const token = await nucleus.getAccessToken(); // valid access token for your backend callsPrefer middleware mounting. If you'd rather not use middleware, mount the catch-all route handler instead —
app/api/auth/[...nucleus]/route.ts:export const GET = nucleus.handler;(then point the routes at/api/auth/*). Both are supported.
Mounted routes
| Route | Purpose |
| ------------------------ | --------------------------------------------------------------- |
| GET /auth/login | Start PKCE; supports ?returnTo= and ?login_hint= |
| GET /auth/callback | Finish PKCE, set the session cookie |
| GET /auth/logout | Revoke the refresh-token family, clear the session |
| GET /auth/profile | Current user JSON (used by useUser()) |
| GET /auth/access-token | Fresh access token JSON (used by the client getAccessToken()) |
API
Server (@typeb-digital/nucleus-nextjs): createNucleusClient(options?) → { middleware, handler, getSession(), getAccessToken(), config }.
Client (@typeb-digital/nucleus-nextjs/client): <NucleusProvider>, useUser(), useAccessToken(), getAccessToken(url?).
createNucleusClient options
All optional except where the env var is required. appId, baseUrl, appBaseUrl, sessionSecret, useRefreshTokens (default true), refreshLeewaySeconds (default 60), routes, cookie, protectedRoutes, postLogoutRedirect.
Refresh & the RSC cookie constraint (important)
React Server Components can read cookies but not write them. A token rotated during an RSC render therefore can't be persisted — and because Nucleus refresh tokens are single-use (a reused one revokes the whole family), silently dropping a rotation is dangerous.
This package solves it the right way: refresh happens in the middleware, which writes the rotated cookie back onto both the request and the response. By the time your RSC renders, the token is already fresh, so getSession()/getAccessToken() are pure reads.
So: use the middleware (the default). If getAccessToken() ever needs to refresh in a context that can't write cookies (e.g. you skipped middleware), it throws a clear error telling you to add it — rather than corrupting the session.
Security notes
- Session lives in an
httpOnly,Secure(whenAPP_BASE_URLis https),SameSite=Lax, JWE-encrypted cookie. The refresh token never reaches the browser. NUCLEUS_SESSION_SECRETmust be strong (openssl rand -hex 32) and kept secret.- Refresh is deduplicated per refresh token within a server instance. Under heavy concurrent refresh on a stateless cookie there's a small race window with rotation; concentrating refresh in middleware avoids it in practice. A pluggable server-side session store may be added for high-concurrency deployments.
License
Proprietary — © Type B Digital.
