Lower-level functions

The single steps useJWSSession() and useJWESession() are made of, for reading, issuing, minting and verifying session tokens by hand.

import { generateJWK, getJWSSession, updateJWSSession, clearJWSSession } from "unjwt/adapters/h3v2";

const key = await generateJWK("HS256");
const config = { key, maxAge: "15m" };

// in a handler
const issued = await updateJWSSession(event, config, { user: "alice" });
// { id: "a1d9390e-…", token: "eyJraWQiOiJ1UklV…", expiresAt: 1792069421000, … }

// in the next request
const session = await getJWSSession(event, config);
// { id: "a1d9390e-…", data: { user: "alice" }, … }

await clearJWSSession(event, config);
// Set-Cookie: h3-jws=; Max-Age=0; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Secure; SameSite=Lax

Both unjwt/adapters/h3v1 and unjwt/adapters/h3v2 export these functions. They take the same config as useJWSSession() and share its per-request state, so a manager and a lower-level call on the same event see the same session.

#Signatures

// JWS sessions
function getJWSSession<T, MaxAge>(
  event,
  config: SessionConfigJWS<T, MaxAge>,
): Promise<SessionJWS<T, MaxAge>>;
function getJWSSessionToken<T, MaxAge>(
  event,
  config: SessionConfigJWS<T, MaxAge>,
): string | undefined;
function updateJWSSession<T, MaxAge>(
  event,
  config: SessionConfigJWS<T, MaxAge>,
  update?: SessionUpdate<T>,
  options?: { maxAge?: ExpiresIn },
): Promise<SessionJWS<T, MaxAge> & { id: string; token: string }>;
function signJWSSession<T, MaxAge>(event, config: SessionConfigJWS<T, MaxAge>): Promise<string>;
function verifyJWSSession<T, MaxAge>(
  event,
  config: SessionConfigJWS<T, MaxAge>,
  token: string,
): Promise<Partial<SessionJWS<T, MaxAge>>>;
function clearJWSSession<T, MaxAge>(event, config: SessionConfigJWS<T, MaxAge>): Promise<void>;

// JWE sessions: the same shapes
(getJWESession,
  getJWESessionToken,
  updateJWESession,
  sealJWESession,
  unsealJWESession,
  clearJWESession);

SessionJWS and SessionJWE are the plain session state (id, createdAt, expiresAt, data, token), without the update() and clear() methods of the manager.

#What each one does

FunctionReads requestWrites cookieFires hooksReturns
getJWSSession / getJWESessionyesonly to expire an expired tokenonRead, onExpire, onErrorThe session state.
getJWSSessionToken / getJWESessionTokenyesnononeThe raw token, or undefined.
updateJWSSession / updateJWESessionif not yet readyesread hooks on a first read, then onUpdate or onErrorThe new state with id and token.
signJWSSession / sealJWESessionif not yet readnoread hooks on a first readA compact token for the current session.
verifyJWSSession / unsealJWESessionnonokey lookup only{ id, createdAt, expiresAt, data }.
clearJWSSession / clearJWESessionnoyes, Max-Age=0onClearNothing.

useJWSSession() is getJWSSession() plus a manager whose update() and clear() call updateJWSSession() and clearJWSSession().

#getJWSSessionToken()

Returns the token the session would read: a cookie this response already set, then the request cookie (chunks joined), then the session header. After a clear() in the same request it returns undefined, header included. It checks nothing. Use it to forward the token to another service that verifies it on its own.

#clearJWSSession()

Expires the cookie whether or not the session was read. onClear receives the session as it stood in this request, so a clear with no earlier read passes oldSession: undefined. Open the session first when the hook needs the old id.

#updateJWSSession()

The same operation as session.update(), for code that has the event and the config but no manager. Inside a hook, session.update() on the manager the hook receives does the same thing.

#signJWSSession() and sealJWESession()

Encode the current session as a compact token without writing a cookie and without firing onUpdate. After an update in the same request the result carries the same jti, iat and exp as the cookie. Use it to hand the token to a client in a response body:

const issued = await updateJWSSession(event, config, { user: "alice" });
const token = await signJWSSession(event, config);
// the same jti as issued.id; for an HMAC key, the same token as issued.token

A session that has no id yet gets a fresh jti on every call, and that jti is not stored. A config with cookie: false and sessionHeader: false has nothing to read, so these functions then encode an empty session: a one-off token with only jti, iat and exp.

#verifyJWSSession() and unsealJWESession()

Open a token that did not come from this request's cookie or header, such as one posted in a body or received over a WebSocket. The checks are those of a session read: the session typ, the pinned alg (and enc), jti and iat, and exp when maxAge is set. The request's session is left alone, and only the key lookup hook runs.

const opened = await verifyJWSSession(event, config, token);
// { id: "7246385f-…", createdAt: 1792068521000, expiresAt: 1792068821000, data: {} }

Failures throw, since there is no onError to receive them:

InputThrows
An expired session tokenERR_JWT_EXPIRED
A malformed tokenERR_SESSION_TOKEN_INVALID, cause ERR_JWS_INVALID
A tampered JWEERR_SESSION_TOKEN_INVALID, cause ERR_JWE_DECRYPTION_FAILED

#Request-like events on h3 v1

The h3 v1 entry also accepts an object that is not an h3 event, as long as it carries context and either headers or request.headers. Reading works. Writing does not, because such an object has no response:

import { getJWSSession, useJWSSession } from "unjwt/adapters/h3v1";

const event = { headers: new Headers({ cookie: `h3-jws=${token}` }), context: {} };
(await getJWSSession(event, config)).id; // "76755fff-…"

const session = await useJWSSession(event, config);
await session.update({ a: 1 });
// JWTError ERR_SESSION_READONLY_EVENT: [unjwt/h3] Cannot update session on read-only event.

#Errors

These functions throw the same codes as useJWSSession(). getJWSSessionToken() and clearJWSSession() also throw ERR_SESSION_CONFIG_INVALID when cookie and sessionHeader are both false. verifyJWSSession() and unsealJWESession() throw where the other functions report to hooks, as listed above.