Adapters

unjwt ships cookie-based session adapters for H3 — the tiny universal HTTP framework behind Nuxt and Nitro. Both H3 v1 and v2 are supported (used by different Nuxt / Nitro major versions).

Import paths:

PathFramework
unjwt/adapters/h3v1H3 v1 — Nuxt v4, Nitro v2
unjwt/adapters/h3v2H3 v2 — Nuxt v5, Nitro v3
unjwt/adapters/h3Alias for h3v1 (will flip to v2 in a future major release)

All three export the same API surface. The internal difference is which h3 peer dependency they're compiled against.

Peer dep: h3^1.15.4 for v1, >=2.0.1-rc.20 || ^2.0.1 for v2.

JWE vs JWS sessions

Each adapter provides two entry points:

FunctionSession storage
useJWESessionJWE — encrypted, opaque to the client.
useJWSSessionJWS — signed, readable by the client.
JWE (encrypted)JWS (signed)
Data visibilityEncrypted, not readable by clientBase64URL-encoded, readable by anyone
Default cookiehttpOnly: true, secure: truehttpOnly: false, secure: true
Key typesPassword string, symmetric JWK, asymmetric keypairSymmetric JWK, asymmetric keypair
Use whenSession data is sensitiveData is non-sensitive; clients need to read it

Rule of thumb:

  • Login / refresh tokens — JWE. The client shouldn't inspect them, and they often contain user-identifiable data.
  • Session data the UI should render from — JWS. Things like role, display name, theme — readable, tamper-evident.
  • Mixing both — common in practice. See the refresh-token pattern.

The session manager

Both useJWESession and useJWSSession return a SessionManager:

interface SessionManager<T, ConfigMaxAge> {
  readonly id: string | undefined; // from jti — undefined until update() is called
  readonly createdAt: number; // from iat, in ms
  readonly expiresAt: ConfigMaxAge extends ExpiresIn ? number : number | undefined;
  readonly data: SessionData<T>; // session payload (excludes jti/iat/exp)
  readonly token: string | undefined; // current raw JWT token
  update: (update?: SessionUpdate<T>) => Promise<SessionManager<T, ConfigMaxAge>>;
  clear: () => Promise<SessionManager<T, ConfigMaxAge>>;
}

Sessions are lazysession.id is undefined until you call session.update(). This is intentional: it matches OAuth and spec-compliant flows where a session only exists once a valid operation has created it.

What else the adapters do

  • Automatic cookie chunking for large tokens (essential for JWE with claims that exceed 4KB).
  • Header-based token extraction as an alternative to cookies (Authorization: Bearer ...).
  • Lifecycle hooksonRead, onUpdate, onClear, onExpire, onError.
  • Key-lookup hooksonVerifyKeyLookup (JWS) / onUnsealKeyLookup (JWE) for key rotation.
  • Lower-level helpersgetJWESession, sealJWESession, unsealJWESession, etc., when you need manual control.
  • Re-exported helpersgenerateJWK, importPEM, exportPEM, deriveJWKFromPassword for convenience.

Going further