Server-side companion to @sendoracloud/sdk-web.
For Next.js (App Router), Remix, SvelteKit, SolidStart, or any framework with a server runtime where Sendora sessions live in HttpOnly cookies. For pure SPA / browser-only apps, use @sendoracloud/sdk-web instead.
Storing session tokens in localStorage (the default for @sendoracloud/sdk-web) is fine for SPAs but exposes them to any XSS payload that runs on your origin. The industry-standard secure posture for framework apps is:
This package wraps all four behind a small surface so your middleware.ts and RSC code stay clean.
npm install @sendoracloud/sdk-web-ssr @sendoracloud/sdk-web
@sendoracloud/sdk-web is an optional peer dependency, only required if you import the ./client subpath (browser-side surface — analytics, feature flags, surveys, in-app messaging, chatbot, auth-from-the-browser). Pure middleware / server / token decode usage doesn't need it.
Targets Node 18+, Bun, Deno, and the Next.js Edge runtime. Zero non-peer runtime dependencies.
| Subpath | Use it from | What it has |
|---|---|---|
./middleware |
middleware.ts (edge runtime) |
sendoraMiddleware() route gate |
./server |
RSC, route handlers, server actions | createSendoraServerClient() |
./client |
client components ("use client") |
re-export of @sendoracloud/sdk-web |
. |
anywhere | low-level token decode + cookie name constants |
The ./client subpath is a pure re-export, not a bundle — @sendoracloud/sdk-web is resolved as a peer dep so version compatibility is explicit and tree-shaking deduplicates it.
// middleware.ts
import { sendoraMiddleware } from "@sendoracloud/sdk-web-ssr/middleware";
export default sendoraMiddleware({
publicKey: process.env.NEXT_PUBLIC_SENDORA_KEY!,
protected: ["/dashboard", "/account"],
publicPaths: ["/login", "/api/public"],
loginPath: "/login",
});
export const config = {
matcher: "/((?!_next/static|_next/image|favicon.ico).*)",
};
Per request: pass-through if cookie is valid, silent rotation if access expired but refresh present, redirect to /login?from=… otherwise.
"use client";
import { SendoraCloud } from "@sendoracloud/sdk-web-ssr/client";
const sendora = SendoraCloud.init({ apiKey: process.env.NEXT_PUBLIC_SENDORA_KEY! });
export function TrackButton() {
return <button onClick={() => sendora.track("button.clicked")}>Click</button>;
}
import { cookies } from "next/headers";
import { createSendoraServerClient } from "@sendoracloud/sdk-web-ssr/server";
export default async function Dashboard() {
const sendora = createSendoraServerClient(cookies(), {
publicKey: process.env.NEXT_PUBLIC_SENDORA_KEY!,
});
const session = sendora.getSession();
if (!session) return <p>Not signed in.</p>;
return <p>Hello {session.email}</p>;
}
"use server";
import { cookies } from "next/headers";
import { createSendoraServerClient } from "@sendoracloud/sdk-web-ssr/server";
export async function signOut() {
const sendora = createSendoraServerClient(cookies(), {
publicKey: process.env.NEXT_PUBLIC_SENDORA_KEY!,
});
await sendora.signOut();
}
Sign-in (email + password, magic link, social, etc.) calls Sendora's /auth-service/login-family endpoints from a server action with credentials: "include". The backend sets the cookies on the response — the framework forwards them automatically.
getSession() decodes the JWT and checks expiry. It does not verify the RS256 signature locally — that would require fetching JWKS on every render and defeat the cookie-cache speed. The Sendora backend re-verifies the signature on every protected API call, which is the authoritative gate. The cookie's HttpOnly + Secure + SameSite attributes guarantee it can only have come from a real backend response.
If you need strict signature verification in middleware (financial dashboards, compliance tooling), use the lower-level decodeAccessToken + your own JWKS fetcher.
Apache-2.0