Migrating to 0.9

The session APIs deprecated during 0.8.x and removed in 0.9.0, each with the code before and after.

Warning

Everything listed here still works in 0.8.x and marks its replacement in its @deprecated tag. Move to the replacements while on 0.8, and the upgrade to 0.9 needs no further edits.

Note

  1. Define each session once with defineJWSSession(config) or defineJWESession(config), and call .use(event) where you called useJWSSession(event, config). See one definition per family.
  2. A session with cookies that reads its token from x-<name>-session sets sessionHeader: true. See the session header.

#Sessions: one definition per family

A session is defined once, from its config, and used on every request. The definition carries the four operations that took an event and a config on every call.

// 0.8
import { useJWSSession } from "unjwt/adapters/h3";

const config = { key, maxAge: "7D" };

app.get("/me", async (event) => {
  const session = await useJWSSession(event, config);
  return session.data;
});

// 0.9
import { defineJWSSession } from "unjwt/adapters/h3";

const useSession = defineJWSSession({ key, maxAge: "7D" });

app.get("/me", async (event) => {
  const session = await useSession.use(event);
  return session.data;
});

A token minted outside the cookie flow, such as a magic link, comes from issue() and needs no request:

// 0.8
const linkConfig = { key, maxAge: "15m", cookie: false, sessionHeader: false };

const token = await signJWSSession(event, linkConfig);
const opened = await verifyJWSSession(event, linkConfig, token);

// 0.9
const magicLink = defineJWSSession({ key, maxAge: "15m", cookie: false, sessionHeader: false });

const token = await magicLink.issue({ email });
const opened = await magicLink.open(token);

On Elysia the plugin is unchanged. jwsSession(config) is defineJWSSession(config).plugin(), and the manager without the plugin comes from use(context):

// 0.8
import { createJWSSession } from "unjwt/adapters/elysia";

const session = await createJWSSession({ cookie, request }, config);

// 0.9
import { defineJWSSession } from "unjwt/adapters/elysia";

const useSession = defineJWSSession(config);

const session = await useSession.use({ cookie, request });

unjwt/adapters/h3, unjwt/adapters/h3v2 and unjwt/adapters/h3v1 (the JWE functions follow the same pattern with defineJWESession):

0.80.9
useJWSSession(event, config)defineJWSSession(config).use(event)
getJWSSession(event, config)defineJWSSession(config).use(event)
getJWSSessionToken(event, config)defineJWSSession(config).token(event)
updateJWSSession(event, config, data, o)(await defineJWSSession(config).use(event)).update(data, o)
clearJWSSession(event, config)(await defineJWSSession(config).use(event)).clear()
signJWSSession(event, config)defineJWSSession(config).issue(data) to mint a token, (await defineJWSSession(config).use(event)).token for the current one
verifyJWSSession(event, config, token)defineJWSSession(config).open(token)
useJWESession(event, config)defineJWESession(config).use(event)
getJWESession(event, config)defineJWESession(config).use(event)
getJWESessionToken(event, config)defineJWESession(config).token(event)
updateJWESession(event, config, data, o)(await defineJWESession(config).use(event)).update(data, o)
clearJWESession(event, config)(await defineJWESession(config).use(event)).clear()
sealJWESession(event, config)defineJWESession(config).issue(data) to mint a token, (await defineJWESession(config).use(event)).token for the current one
unsealJWESession(event, config, token)defineJWESession(config).open(token)

unjwt/adapters/elysia:

0.80.9
createJWSSession(context, config)defineJWSSession(config).use(context)
createJWESession(context, config)defineJWESession(config).use(context)
jwsSession(config)jwsSession(config), or defineJWSSession(config).plugin()
jweSession(config)jweSession(config), or defineJWESession(config).plugin()

getJWSSession returned the session state and updateJWSSession the reissued state. The manager use() returns exposes the same id, createdAt, expiresAt, data and token, read live.

signJWSSession and sealJWESession encoded the event's current session. issue(data) mints a token from the data it is given, with a fresh jti and iat, and reads no request. To send the token the request already carries, read token from the manager.

verifyJWSSession(event, …) and unsealJWESession(event, …) handed { event } ({ context } on Elysia) to onVerifyKeyLookup and onUnsealKeyLookup. open(token) takes no request, so those hooks receive the header and the config alone; a lookup that needs the request belongs with use(event).

#The h3 adapters

unjwt/adapters/h3, unjwt/adapters/h3v2 and unjwt/adapters/h3v1 are removed. An h3 v2 event is the web-standard { req, res, context } that unjwt/session takes as it is, and since Nuxt 4.6 the handlers from nuxt/server hand the same event on Nitro 2 and Nitro 3. One entry, no h3 peer dependency, and Nitro apps stop depending on h3 they never installed.

// 0.8: h3 v2, Nitro 3, Nuxt 5
import { defineJWESession } from "unjwt/adapters/h3";

// 0.9
import { type WebSessionEvent, defineJWESession } from "unjwt/session";

const session = defineJWESession({ key, maxAge: "7D" });
export const useAppSession = (event: WebSessionEvent) => session.use(event);

The h3 v2 adapter mirrored the session's Set-Cookie into res.errHeaders, the headers h3 sends with a thrown handler's error. unjwt/session does the same on its own: when the event's res carries an errHeaders Headers, as h3 v2's does, every cookie write and drop lands in both, so a handler that throws after update() or clear() needs no hook.

import type { H3Event } from "h3";

const session = defineJWESession<{ user: string }, "7D", H3Event>({ key, maxAge: "7D" });

The third generic narrows use() and every hook's event to the framework's event, as the adapter's TEvent did, and the hook argument keeps its name. Nitro answers errors with a Response of its own, which h3 sends without errHeaders, so there neither the adapter nor the entry makes a difference.

Two things change with the entry. The default cookie name is unjwt-jwe / unjwt-jws where the adapter used h3-jwe / h3-jws: set name: "h3-jwe" to keep reading the cookies already out there, or leave it and every user signs in again once, their old cookie ignored until it expires. And error messages start with [unjwt/session] instead of [unjwt/h3]; the codes are the same.

On Nuxt 4 the handler itself changes: h3's auto-imported defineEventHandler passes the h3 v1 event, which unjwt/session and every nuxt/server helper refuse.

// 0.8: server/utils/session.ts
import type { H3Event } from "h3";
import { type CompatEvent, defineJWESession } from "unjwt/adapters/h3v1";

const session = defineJWESession({ key, maxAge: "7D" });
export const useAppSession = (event: H3Event | CompatEvent) => session.use(event);

// 0.9: server/utils/session.ts
import type { RequestEvent } from "nuxt/server";
import { defineJWESession } from "unjwt/session";

const session = defineJWESession({ key, maxAge: "7D" });
export const useAppSession = (event: RequestEvent) => session.use(event);
// 0.8: server/api/me.get.ts, h3's auto-imported defineEventHandler
export default defineEventHandler(async (event) => (await useAppSession(event)).data);

// 0.9: the handler comes from nuxt/server, so the event is a RequestEvent
import { defineEventHandler } from "nuxt/server";

export default defineEventHandler(async (event) => (await useAppSession(event)).data);

A WebSocket upgrade request goes to use() as { headers: request.headers, context: request.context, res: { headers } } on every runtime, and the { headers, context } shape CompatEvent named is a SessionRequestEvent on unjwt/session. See Frameworks for each runtime.

#The session header is named

Left unset, sessionHeader lets a session with cookies read its token from x-<name>-session when the cookie carries none. In 0.9 that session reads cookies only unless the header is named. Set sessionHeader: true for x-<name>-session, or a header name of your own.

// 0.8: reads x-app-session, else the cookie
defineJWSSession({ name: "app", key });

// 0.9: same behaviour
defineJWSSession({ name: "app", key, sessionHeader: true });

A session with cookie: false keeps deriving x-<name>-session when sessionHeader is unset; it needs no edit.

#Deprecations

Deprecated in 0.8.2 and removed in 0.9.0. Each still works in 0.8.

SymbolReplacement
useJWSSession, useJWESession (unjwt/adapters/h3v1, h3v2)defineJWSSession(config).use(event)
getJWSSession, getJWESession (unjwt/adapters/h3v1, h3v2)defineJWSSession(config).use(event)
getJWSSessionToken, getJWESessionToken (h3v1, h3v2)defineJWSSession(config).token(event)
updateJWSSession, updateJWESession (h3v1, h3v2)(await defineJWSSession(config).use(event)).update()
clearJWSSession, clearJWESession (h3v1, h3v2)(await defineJWSSession(config).use(event)).clear()
signJWSSession, sealJWESession (h3v1, h3v2)defineJWSSession(config).issue(data)
verifyJWSSession, unsealJWESession (h3v1, h3v2)defineJWSSession(config).open(token)
createJWSSession, createJWESession (unjwt/adapters/elysia)defineJWSSession(config).use(context)
implicit sessionHeader with cookies on (unjwt/adapters/*)sessionHeader: true
unjwt/adapters/h3, h3v2, h3v1, every exportunjwt/session (see the h3 adapters)
exportKey (unjwt/jwk)exportJWK, same arguments and result