Skip to main content

ssr

Framework-agnostic server (SSR) building blocks for Convex Auth: a cookie abstraction, a WHATWG Request/Response cookie adapter, access-token inspection, the ServerAuthSession that owns the refresh lifecycle, the createServerAuthChecker that verifies an access token against the backend, the method-agnostic (Request) => Response refresh/sign-out handlers, the convexProxyHandler that serves sign-in, and the setupConvexAuthServer factory that configures them all once.

Classes​

Interfaces​

Type Aliases​

AuthCookieOptions​

type AuthCookieOptions = CookieDeleteOptions & object;

The cookie options a framework integration may supply for auth cookies: only the attributes that legitimately vary by deployment.

secure is required. Whether auth cookies are HTTPS-only depends on the deployment (HTTPS in production, plain http on localhost), so every integration has to decide it explicitly rather than inherit a default that would be wrong in one environment or the other.

The security-relevant attributes (httpOnly, sameSite) and the token lifetimes (maxAge, expires, which derive from the token bundle) are deliberately not configurable; writeAuthCookies always applies the invariant values.

Type Declaration​

secure​
secure: boolean;

AuthSessionResponse​

type AuthSessionResponse = object;

The JSON body of every SSR auth session response: the refresh handler, the sign-out handler, and the cross-site refusal all reply with this shape, and the client parses it without looking at the status. tokens is null whenever there is no live session to report (a dead or missing refresh cookie, a completed sign-out, a refused origin).

Properties​

PropertyType
tokensSlimTokenBundle | null

ClientView​

type ClientView<T> = T extends object ? Omit<T, "tokens"> & object : T;

A sign-in result as clients see it, with the token bundle narrowed to what every session model delivers.

Under SSR the auth proxy strips the refresh token before the response reaches the browser, so the slim bundle is all the two models have in common. Declaring that here makes the stripping a widening of the value rather than a broken contract: a TokenBundle is assignable to a SlimTokenBundle, so one type stays true for both.

It also stops callers depending on the refresh token. Handing the bundle to setSession is the only supported use; an app that needs the raw token can call its Convex function with the generated reference and get the full type.

Distributes over a result union, leaving failure arms untouched.

Type Parameters​

Type Parameter
T

CookieDeleteOptions​

type CookieDeleteOptions = Pick<CookieOptions, "path" | "domain">;

The attributes that identify which cookie a deletion targets: browsers only remove a cookie when the deletion's path and domain match the ones it was written with. No other attribute takes part in matching.


ExposedSignInFn​

type ExposedSignInFn = FunctionReference<"mutation" | "action", "public", DefaultFunctionArgs, SignInEnvelope>;

A sign-in function exposed through the proxy.

Any public mutation or action returning the shared SignInEnvelope. Args are typed loosely because the proxy never inspects them; it forwards the caller's encoded args untouched.


IsAuthenticatedFn​

type IsAuthenticatedFn = FunctionReference<"query", "public", Record<string, never>, boolean>;

The app's isAuthenticated query reference: reports whether the access token the query is called with identifies a signed-in user.


RefreshResult​

type RefreshResult = Infer<typeof vRefreshResult>;

The result of refreshing a session, returned by the app's refreshSession mutation and by ServerAuthSession.refresh(). Check kind:

  • { kind: "rotated", tokens }: tokens is the new TokenBundle, with a fresh access token and the refresh token that replaces the one presented. Persist both. The presented refresh token stays valid until its successor is first used, so a client that never receives this result can retry with the token it already has.
  • { kind: "noSession" }: the refresh token is unknown, expired, or superseded (a token issued after it has already been used). Only a superseded token revokes anything: it is what a leaked token looks like, so presenting it ends its session for every holder. An unknown or expired token has no live session left to revoke. Clear the stored session and treat the user as signed out.

RefreshSession​

type RefreshSession = (refreshToken) => Promise<RefreshResult>;

Exchange a refresh token for a fresh session, resolving to one of the RefreshResult outcomes. A framework binding usually implements this over a Convex HTTP client; tests pass a fake.

Parameters​

ParameterType
refreshTokenstring

Returns​

Promise<RefreshResult>


RefreshSessionFn​

type RefreshSessionFn = FunctionReference<"mutation", "public", {
refreshToken: string;
}, RefreshResult>;

The app's refreshSession mutation reference: exchange a refresh token for a fresh session. See RefreshResult for the outcomes.


RequestHandler​

type RequestHandler = (request) => Promise<Response>;

A WHATWG request handler.

Parameters​

ParameterType
requestRequest

Returns​

Promise<Response>


ServerAuthChecker​

type ServerAuthChecker = (accessToken) => Promise<boolean>;

Reports whether a JWT accessToken is a valid, signed-in session (null = no token = not signed in).

Parameters​

ParameterType
accessTokenstring | null

Returns​

Promise<boolean>


SignInComplete​

type SignInComplete = Infer<typeof vSignInComplete>;

SignInEnvelope​

type SignInEnvelope =
| SignInComplete
| SignInError<unknown>;

The full shared sign-in envelope.

Represents sign-ins that ran to completion and those that hit an error.

userError is unknown here because each method declares its own codes.


SignInError​

type SignInError<UserError> = object;

The inferred shape of a vSignInError arm.

Type Parameters​

Type Parameter
UserError

Properties​

PropertyType
status"error"
userErrorUserError

SignInStatus​

type SignInStatus = SignInEnvelope["status"];

The status discriminant of every SignInEnvelope.


SignOutFn​

type SignOutFn = FunctionReference<"mutation", "public", {
refreshToken: string;
}, null>;

The app's signOut mutation reference: revoke the session for a refresh token.


SlimTokenBundle​

type SlimTokenBundle = object;

The token bundle that an SSR route hands back to the client. It is a slimmed down version of a TokenBundle with the refresh token (and its expiry) removed: under SSR the refresh token lives only in an httpOnly cookie and never reaches client JS.

Properties​

PropertyType
accessTokenstring
userIdstring

Variables​

const AUTH_JWT_COOKIE: "__convexAuthJWT" = JWT_STORAGE_KEY;

The cookie holding the current access token (a JWT).


const AUTH_REFRESH_COOKIE: "__convexAuthRefreshToken" = REFRESH_TOKEN_STORAGE_KEY;

The cookie holding the current (rotating) refresh token.

Functions​

clearAuthCookies()​

function clearAuthCookies(cookies, options): Promise<void>;

Delete both auth cookies. The counterpart of writeAuthCookies: it takes the same options so the deletions match the cookies the writes produced (a deletion only removes a cookie whose path/domain match).

Parameters​

ParameterType
cookiesCookieStore
optionsAuthCookieOptions

Returns​

Promise<void>


convexProxyHandler()​

function convexProxyHandler(config): RequestHandler;

Build the route that serves every allowlisted sign-in function.

Mount it once, at a static path of your choosing. Adding an auth method means adding its function to ConvexProxyConfig.signIn, not adding another route.

// app/auth/signin/route.ts
import { auth } from "@/auth"; // the result of `setupConvexAuthServer`

export const POST = auth.convexProxyHandler;

Parameters​

ParameterType
configConvexProxyConfig

Returns​

RequestHandler


createServerAuthChecker()​

function createServerAuthChecker(config): ServerAuthChecker;

Build a ServerAuthChecker that verifies a JWT access token against the backend.

The returned checker treats an absent or already-expired token as unauthenticated without a round trip (a cheap local gate); otherwise it asks the backend, which verifies the token's signature. Any error (network, rejected token) fails closed to false.

Parameters​

ParameterType
configServerAuthCheckerConfig

Returns​

ServerAuthChecker


decodeAccessToken()​

function decodeAccessToken(token): DecodedAccessToken;

Decode an access token's claims without verifying its signature. Returns nulls if the token is malformed.

Parameters​

ParameterType
tokenstring

Returns​

DecodedAccessToken


httpCookies()​

function httpCookies(request): HttpCookies;

Adapt an incoming Request's cookies to a CookieStore.

The returned HttpCookies object allows applying changes to the cookies via the HttpCookies#applyTo function. Pass it Response Headers and it will write the cookie values.

const client = new ConvexHttpClient(process.env.CONVEX_URL!);

export async function POST(request: Request) {
const cookies = httpCookies(request);
const session = new ServerAuthSession({
refreshSession: (refreshToken) =>
client.mutation(api.auth.refreshSession, { refreshToken }),
cookies,
cookieOptions: { secure: true },
});
const result = await session.refresh();
const res = Response.json({
tokens: result.kind === "rotated" ? makeSlimBundle(result.tokens) : null,
});
cookies.applyTo(res.headers);
return res;
}

Parameters​

ParameterType
requestRequest

Returns​

HttpCookies


isTokenExpiring()​

function isTokenExpiring(
token,
skewSeconds?,
nowMs?
): boolean;

Whether a token is expired or within skewSeconds of expiry (so the caller should refresh). A token whose expiry can't be read is treated as expiring.

Parameters​

ParameterTypeDefault value
tokenstringundefined
skewSecondsnumber10
nowMsnumber...

Returns​

boolean


isTrustedOrigin()​

function isTrustedOrigin(request, allowedOrigins?): boolean;

Whether a request may be served by a state-changing auth handler.

Trusts a request when its Origin header matches the Host header it arrived at or one of allowedOrigins (for deployments where the two legitimately differ, e.g. a proxy that rewrites Host). Entries may be full origins ("https://app.example.com") or bare hosts ("app.example.com", "localhost:3000").

Parameters​

ParameterTypeDefault value
requestRequestundefined
allowedOriginsstring[][]

Returns​

boolean


makeSlimBundle()​

function makeSlimBundle(bundle): SlimTokenBundle;

Strip down a TokenBundle down to a SlimTokenBundle, dropping the refresh token so it is never sent to the browser.

Parameters​

ParameterType
bundle{ accessToken: string; refreshToken: string; refreshTokenExpiresAt: number; userId: string; }
bundle.accessTokenstring
bundle.refreshTokenstring
bundle.refreshTokenExpiresAtnumber
bundle.userIdstring

Returns​

SlimTokenBundle


refreshHandler()​

function refreshHandler(config): RequestHandler;

A handler that refreshes the session from the httpOnly refresh-token cookie, replying with an AuthSessionResponse carrying the fresh access token. When there is no session left (missing or unrecognized refresh cookie) it replies 401 with tokens: null.

Parameters​

ParameterType
configRefreshHandlerConfig

Returns​

RequestHandler


serializeCookie()​

function serializeCookie(
name,
value,
options?
): string;

Serialize a cookie into a Set-Cookie header value.

Thin adapter over the cookie package's serialize that maps our CookieOptions onto it.

Deletion is expressed by the caller as an empty value with maxAge: 0 (and a past expires); see httpCookies's delete.

Parameters​

ParameterType
namestring
valuestring
optionsCookieOptions

Returns​

string


setupConvexAuthServer()​

function setupConvexAuthServer(config): object;

Configure the auth handlers once and get back the routes to mount.

// auth.ts
export const auth = setupConvexAuthServer({
convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
refreshSession: api.auth.refreshSession,
signOut: api.auth.signOut,
signIn: [
api.auth.signInWithUsernamePassword,
api.auth.signUpWithUsernamePassword,
],
cookieOptions: { secure: process.env.NODE_ENV === "production" },
});

Then mount the routes that it returns. For example, the sign-in route serves every method listed in signIn:

// app/auth/signin/route.ts
import { auth } from "@/auth"; // the result of `setupConvexAuthServer`

export const POST = auth.convexProxyHandler;

Parameters​

ParameterType
configConvexAuthServerConfig

Returns​

convexProxyHandler​
convexProxyHandler: RequestHandler;

The sign-in route.

Mount it once, at a static path, and every function in signIn is reachable from the normal client hook of its method.

// app/auth/signin/route.ts
import { auth } from "@/auth"; // the result of `setupConvexAuthServer`

export const POST = auth.convexProxyHandler;
refreshHandler​
refreshHandler: RequestHandler;
signOutHandler​
signOutHandler: RequestHandler;

signOutHandler()​

function signOutHandler(config): RequestHandler;

A handler that revokes the session from the httpOnly refresh-token cookie (best effort) and clears both cookies, replying with an AuthSessionResponse carrying tokens: null.

Parameters​

ParameterType
configSignOutHandlerConfig

Returns​

RequestHandler


writeAuthCookies()​

function writeAuthCookies(
cookies,
bundle,
options
): Promise<void>;

Write the tokens in a bundle as cookies.

The access token is stored in AUTH_JWT_COOKIE and the refresh token in AUTH_REFRESH_COOKIE. The invariant attributes (httpOnly etc.) are merged in, so the refresh cookie is httpOnly and never reaches client JS.

This is the one place that knows which cookies hold a session, so refresh, the proxy, and every method's sign-in handler write cookies identically.

Parameters​

ParameterType
cookiesCookieStore
bundle{ accessToken: string; refreshToken: string; refreshTokenExpiresAt: number; userId: string; }
bundle.accessTokenstring
bundle.refreshTokenstring
bundle.refreshTokenExpiresAtnumber
bundle.userIdstring
optionsAuthCookieOptions

Returns​

Promise<void>

References​

ConvexAuthApi​

Re-exports ConvexAuthApi


TokenBundle​

Re-exports TokenBundle