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
- ConvexAuthServerConfig
- ConvexProxyConfig
- CookieOptions
- CookieStore
- DecodedAccessToken
- HttpCookies
- RefreshHandlerConfig
- ServerAuthCheckerConfig
- ServerAuthSessionConfig
- SignOutHandlerConfig
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
| Property | Type |
|---|---|
tokens | SlimTokenBundle | 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 }:tokensis 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
| Parameter | Type |
|---|---|
refreshToken | string |
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
| Parameter | Type |
|---|---|
request | Request |
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
| Parameter | Type |
|---|---|
accessToken | string | 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
| Property | Type |
|---|---|
status | "error" |
userError | UserError |
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
| Property | Type |
|---|---|
accessToken | string |
userId | string |
Variables
AUTH_JWT_COOKIE
const AUTH_JWT_COOKIE: "__convexAuthJWT" = JWT_STORAGE_KEY;
The cookie holding the current access token (a JWT).
AUTH_REFRESH_COOKIE
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
| Parameter | Type |
|---|---|
cookies | CookieStore |
options | AuthCookieOptions |
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
| Parameter | Type |
|---|---|
config | ConvexProxyConfig |
Returns
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
| Parameter | Type |
|---|---|
config | ServerAuthCheckerConfig |
Returns
decodeAccessToken()
function decodeAccessToken(token): DecodedAccessToken;
Decode an access token's claims without verifying its signature. Returns nulls if the token is malformed.
Parameters
| Parameter | Type |
|---|---|
token | string |
Returns
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
| Parameter | Type |
|---|---|
request | Request |
Returns
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
| Parameter | Type | Default value |
|---|---|---|
token | string | undefined |
skewSeconds | number | 10 |
nowMs | number | ... |
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
| Parameter | Type | Default value |
|---|---|---|
request | Request | undefined |
allowedOrigins | string[] | [] |
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
| Parameter | Type |
|---|---|
bundle | { accessToken: string; refreshToken: string; refreshTokenExpiresAt: number; userId: string; } |
bundle.accessToken | string |
bundle.refreshToken | string |
bundle.refreshTokenExpiresAt | number |
bundle.userId | string |
Returns
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
| Parameter | Type |
|---|---|
config | RefreshHandlerConfig |
Returns
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
| Parameter | Type |
|---|---|
name | string |
value | string |
options | CookieOptions |
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
| Parameter | Type |
|---|---|
config | ConvexAuthServerConfig |
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
| Parameter | Type |
|---|---|
config | SignOutHandlerConfig |
Returns
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
| Parameter | Type |
|---|---|
cookies | CookieStore |
bundle | { accessToken: string; refreshToken: string; refreshTokenExpiresAt: number; userId: string; } |
bundle.accessToken | string |
bundle.refreshToken | string |
bundle.refreshTokenExpiresAt | number |
bundle.userId | string |
options | AuthCookieOptions |
Returns
Promise<void>
References
ConvexAuthApi
Re-exports ConvexAuthApi
TokenBundle
Re-exports TokenBundle