Next.js
The plug-and-play bridge: `GET`/`POST`, `getConnectSession`, the cookie session store.
Generated from @cosmoops/connect-sdk/next's TypeScript source and its own doc comments - see Reference for the other entry points. Regenerated on every deploy; don't hand-edit.
Interfaces
Interface: ConnectSessionOptions
getConnectSession's options - the same three escape hatches everywhere.
Properties
| Property | Type | Description |
|---|---|---|
client? | ConnectClient | - |
session? | ConnectSessionStore | - |
account? | | ConnectAccountSessionOptions | null | Account mode; defaults to the environment, null forces application mode. |
Interface: CreateConnectHandlersOptions
Properties
| Property | Type | Description |
|---|---|---|
client? | ConnectClient | - |
session? | ConnectSessionStore | - |
account? | | ConnectAccountSessionOptions | null | Account mode. Defaults to the environment (CONNECT_SESSION_MODE=account plus CONNECT_ACCOUNT_COOKIE[_DOMAIN]); null forces application mode. |
basePath? | string | - |
defaultRedirect? | string | Where /callback lands when the request carried no next. Defaults to /. |
Functions
Function: connectAccountSession()
function connectAccountSession():
| ConnectAccountSessionOptions
| null;The account-session options from the environment, or null in application mode.
Returns
| ConnectAccountSessionOptions
| null
Function: connectClient()
function connectClient(overrides?: Partial<ConnectClientOptions>): ConnectClient;The shared server client, built from the environment on first use.
Lazy on purpose: reading CONNECT_API_KEY at module load would throw during
next build, where route modules are imported but never invoked and secrets
are frequently absent.
Parameters
| Parameter | Type |
|---|---|
overrides | Partial<ConnectClientOptions> |
Returns
Function: connectProviderProps()
function connectProviderProps(): {
appUrl: string;
};The ConnectProvider props that come from the environment.
// app/layout.tsx - a Server Component
import { connectProviderProps } from "@cosmoops/connect-sdk/next";
import { ConnectProvider } from "@cosmoops/connect-sdk/client";
<ConnectProvider {...connectProviderProps()}>{children}</ConnectProvider>What it is for
CONNECT_APP_URL points the server at a non-production Connect. It is not
NEXT_PUBLIC_, so it never reaches the browser, and ConnectProvider
defaults appUrl to the production origin independently - so pointing the
server somewhere else and leaving the prop off splits the integration in
half. The server talks to dev and every destination the browser builds opens
production: the user menu's Profile and Billing rows, the app launcher's link
for an unsubscribed application, ConnectAccessGate's subscribe target, and
checkout. Nothing errors. The customer is simply asked to buy a second
subscription in an instance where they have none.
Both apps built on this SDK shipped that bug before anyone noticed, from
reading "the provider takes no props" as a rule rather than as a remark about
clientId. A root layout is a Server Component, so one spread fixes it with
no browser-visible copy of the variable.
What it deliberately leaves out
clientId and basePath. The first is only needed alongside an explicit
redirectUri, when you build the authorize URL in the browser rather than
letting signIn() bounce through your own /signin route - and handing the
browser a value it does not need is surface for nothing. The second is where
you mounted the handler, which the environment does not know. Pass either
yourself; a prop you pass always wins over this spread, whichever order you
write them in, as long as the spread comes first.
If you already have an origin helper
Use it instead. An app that builds its own links into Connect (a "Manage team" button, a pricing page's subscribe target) needs that origin on the server too, and one function owning the read is better than this helper beside it - two readers of one variable is the shape that caused this. This exists for the integration that has no such helper, which is most of them.
Returns
{
appUrl: string;
}appUrl
appUrl: string;Function: connectSessionCookieName()
function connectSessionCookieName(clientId: string): string;This application's own session cookie name: connect_session_ plus the
last 12 characters of its clientId. Distinct per application, so two apps
sharing a host cannot overwrite each other's session.
Parameters
| Parameter | Type |
|---|---|
clientId | string |
Returns
string
Function: connectSessionMode()
function connectSessionMode(): "application" | "account";CONNECT_SESSION_MODE: "application" (the default, today's behaviour) or
"account". Any other value throws rather than quietly picking one - a
typo here would otherwise sign every user out, or never sign them in.
Returns
"application" | "account"
Function: cookieSessionStore()
function cookieSessionStore(cookieName?: string): ConnectSessionStore;An httpOnly cookie holding the opaque sessionToken.
Not signed or encrypted, deliberately. The token is opaque and useless
without the client_secret that never leaves the server, so a tampered
value buys an attacker a 401 rather than a forged identity - and a signing
secret would be one more thing to rotate for protection already provided by
the token's own design. SameSite=Lax because Strict would withhold the
cookie on the redirect back from Connect.
The name
An explicit cookieName, else CONNECT_SESSION_COOKIE, is used exactly.
Otherwise the name is per application - connectSessionCookieName(CONNECT_CLIENT_ID)
- with the legacy shared
connect_sessionread as a fallback: a token found only there is rewritten under the new name once Connect accepts it (migrate, fromGET /session). The legacy cookie is never written or cleared - on a shared host another app may still own it, and if it is not this app's token Connect refuses it and the user simply signs in. With noCONNECT_CLIENT_IDthe name staysconnect_session.
Parameters
| Parameter | Type |
|---|---|
cookieName? | string |
Returns
Function: createConnectHandlers()
function createConnectHandlers(options?: CreateConnectHandlersOptions): {
GET: (request: Request) => Promise<Response>;
POST: (request: Request) => Promise<Response>;
};The catch-all route handlers, ready to re-export.
Only needed when you are changing something - a different mount point, a
session store that isn't a cookie, a post-login destination that isn't /.
The zero-configuration case is the GET / POST re-export below, which is
this function with no arguments.
// app/api/connect/[...connect]/route.ts
import { createConnectHandlers } from "@cosmoops/connect-sdk/next";
export const { GET, POST } = createConnectHandlers({ basePath: "/api/auth" });GET and POST are the same function - the bridge routes on method and
path itself, so Next only needs to know both verbs reach it.
Parameters
| Parameter | Type |
|---|---|
options | CreateConnectHandlersOptions |
Returns
{
GET: (request: Request) => Promise<Response>;
POST: (request: Request) => Promise<Response>;
}GET
GET: (request: Request) => Promise<Response> = handler;Parameters
| Parameter | Type |
|---|---|
request | Request |
Returns
Promise<Response>
POST
POST: (request: Request) => Promise<Response> = handler;Parameters
| Parameter | Type |
|---|---|
request | Request |
Returns
Promise<Response>
Function: GET()
function GET(request: Request): Promise<Response>;The bridge itself, for the default mount at /api/connect.
Built on the first request rather than at module load, for the same reason
connectClient is lazy: next build imports every route module, and an
environment-reading constructor at the top level turns a missing secret in
CI into a failed build of routes that were never going to run there.
Parameters
| Parameter | Type |
|---|---|
request | Request |
Returns
Promise<Response>
Function: getConnectSession()
function getConnectSession(request?: Request, options?: ConnectSessionOptions): Promise<UserResponse | null>;The signed-in user for a Server Component, Route Handler or Server Action,
or null.
// app/dashboard/page.tsx
const session = await getConnectSession();
if (!session) redirect("/api/connect/signin?next=/dashboard");requireConnectSession() below is that exact check in one call, for the
common case of "redirect if signed out."
Pass a Request to use it somewhere next/headers does not exist - a
framework-agnostic handler, a test, a custom server. Both forms take the
same path afterwards; the argument is only how the cookie is found.
Reads cookies() transitively, so under cacheComponents this belongs
inside a <Suspense> boundary (or a 'use cache: private' function) like
any other request-scoped read - a page that awaits it above every boundary
gives up its static shell.
Returns null when there is no session - no cookie, or one Connect
reports as expired, revoked or issued to another application. A page guard
needs no try/catch for those, which are the cases where sending someone
back through sign-in is the right answer.
It throws on anything else, and that is deliberate. It previously
swallowed every failure into null, so a rate-limited or unreachable
Connect read as "signed out" - and requireConnectSession then redirected a
signed-in user to sign-in, whose OAuth round trip spent more of the same
exhausted budget and 429'd again on return. A login loop that could only end
when the rate-limit window rolled. An error boundary is the honest outcome
for "could not ask", and unlike a spurious sign-out it is recoverable.
Memoized per request for the plain, zero-argument call - the shape
every page, layout and Server Action actually uses - via React's cache().
Passing an explicit request, client or session bypasses the cache
and always makes a live call: those are for tests and non-ambient
callers, where a fresh read every time is the point, not an oversight.
Parameters
| Parameter | Type |
|---|---|
request? | Request |
options? | ConnectSessionOptions |
Returns
Promise<UserResponse | null>
Function: getConnectSessionToken()
function getConnectSessionToken(request?: Request, options?: {
session?: ConnectSessionStore;
account?: | ConnectAccountSessionOptions
| null;
}): Promise<string | null>;The caller's opaque Connect session token, or null when signed out.
What it is for
Every asUser call on the server client needs one - workspaces.*,
scheduling.*, the /data store's per-user form - and until this existed
there was no way to obtain it inside a Server Component. getConnectSession
answers who the caller is and deliberately does not hand back the
credential, so a page that wanted to read its own workspace's roster had to
either reimplement the cookie read or route the call through the browser.
// app/settings/page.tsx
const token = await getConnectSessionToken();
const members = token ? await connect.workspaces.members({ asUser: token }) : [];It is the cookie, unverified
This reads the session store and nothing else - no round trip, and no
check that the token is still live. A token from a revoked or expired
session is a string that looks exactly like a good one, and Connect is what
answers 401 for it. So this is not a sign-in check: use
getConnectSession() for "is somebody here", and this for "hand Connect the
credential they arrived with". A non-null answer means a cookie was present,
not that it works.
Reads cookies() transitively, so under cacheComponents it belongs inside
a <Suspense> boundary like any other request-scoped read.
Pass a Request to use it where next/headers does not exist, and a
session store if you did not use the default cookie - the same two escape
hatches getConnectSession takes, for the same reasons.
In account mode this is the account-session id (or, during migration, a legacy per-application token), so a per-token cache keyed on it keeps working. It is the credential the request carries, not necessarily an application session token.
Parameters
| Parameter | Type |
|---|---|
request? | Request |
options? | { session?: ConnectSessionStore; account?: | ConnectAccountSessionOptions | null; } |
options.session? | ConnectSessionStore |
options.account? | | ConnectAccountSessionOptions | null |
Returns
Promise<string | null>
Function: requireConnectSession()
function requireConnectSession(request?: Request, options?: ConnectSessionOptions & {
signInPath?: string;
next?: string;
}): Promise<UserResponse>;getConnectSession(), minus the boilerplate of checking it and redirecting
yourself:
// app/dashboard/page.tsx
const session = await requireConnectSession();Redirects to /signin under the same basePath as getConnectSession's
default (/api/connect) when signed out - pass signInPath if you mounted
the bridge somewhere else, and next to send the visitor back to where
they were once they're signed in. Only sensible in a Server Component,
Route Handler or Server Action, the same three places redirect() from
next/navigation itself works.
A thin wrapper, not a framework of its own - reach for getConnectSession
plus your own check when "not signed in" isn't "redirect to sign-in"
(an API route returning 401, say). Role and entitlement checks stay
outside this function on purpose: session.permissions and
connect.entitlements.* are one line each on top of the session this
already gives you, and where that line redirects (a plans page, a
"forbidden" screen) is a decision only your app can make - see
"Authentication & authorization" for the pattern.
Parameters
| Parameter | Type |
|---|---|
request? | Request |
options? | ConnectSessionOptions & { signInPath?: string; next?: string; } |
Returns
Promise<UserResponse>