
# Lower-level functions

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

```ts
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()`](/adapters/h3) and share its per-request state, so a manager and a lower-level call on the same event see the same session.

## Signatures

```ts
// 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

| Function                                    | Reads request   | Writes cookie                   | Fires hooks                                              | Returns                                  |
| ------------------------------------------- | --------------- | ------------------------------- | -------------------------------------------------------- | ---------------------------------------- |
| `getJWSSession` / `getJWESession`           | yes             | only to expire an expired token | `onRead`, `onExpire`, `onError`                          | The session state.                       |
| `getJWSSessionToken` / `getJWESessionToken` | yes             | no                              | none                                                     | The raw token, or `undefined`.           |
| `updateJWSSession` / `updateJWESession`     | if not yet read | yes                             | read hooks on a first read, then `onUpdate` or `onError` | The new state with `id` and `token`.     |
| `signJWSSession` / `sealJWESession`         | if not yet read | no                              | read hooks on a first read                               | A compact token for the current session. |
| `verifyJWSSession` / `unsealJWESession`     | no              | no                              | key lookup only                                          | `{ id, createdAt, expiresAt, data }`.    |
| `clearJWSSession` / `clearJWESession`       | no              | yes, `Max-Age=0`                | `onClear`                                                | Nothing.                                 |

`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:

```ts
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.

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

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

| Input                    | Throws                                                           |
| ------------------------ | ---------------------------------------------------------------- |
| An expired session token | `ERR_JWT_EXPIRED`                                                |
| A malformed token        | `ERR_SESSION_TOKEN_INVALID`, `cause` `ERR_JWS_INVALID`           |
| A tampered JWE           | `ERR_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:

```ts
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()`](/adapters/h3#errors). `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.
