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:
| Path | Framework |
|---|---|
unjwt/adapters/h3v1 | H3 v1 — Nuxt v4, Nitro v2 |
unjwt/adapters/h3v2 | H3 v2 — Nuxt v5, Nitro v3 |
unjwt/adapters/h3 | Alias 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:
| Function | Session storage |
|---|---|
useJWESession | JWE — encrypted, opaque to the client. |
useJWSSession | JWS — signed, readable by the client. |
| JWE (encrypted) | JWS (signed) | |
|---|---|---|
| Data visibility | Encrypted, not readable by client | Base64URL-encoded, readable by anyone |
| Default cookie | httpOnly: true, secure: true | httpOnly: false, secure: true |
| Key types | Password string, symmetric JWK, asymmetric keypair | Symmetric JWK, asymmetric keypair |
| Use when | Session data is sensitive | Data 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 lazy — session.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 hooks —
onRead,onUpdate,onClear,onExpire,onError. - Key-lookup hooks —
onVerifyKeyLookup(JWS) /onUnsealKeyLookup(JWE) for key rotation. - Lower-level helpers —
getJWESession,sealJWESession,unsealJWESession, etc., when you need manual control. - Re-exported helpers —
generateJWK,importPEM,exportPEM,deriveJWKFromPasswordfor convenience.
Going further
- H3 sessions → —
useJWESession/useJWSSessionconfiguration in detail. - Lifecycle hooks → — logging, revocation, key rotation.
- Lower-level functions → — manual session control.
- Example: refresh token pattern →.